Heisenberg
Heisenberg

Reputation: 87

Cannot bind list of items in GridView column

I'm building an application that show to user the live result of a matches series. I setup the structure of data as follows: Countries->Leagues->Matches In particular in the ViewModel I've created an observable collection of countries as follows:

private ObservableCollection<Models.Country> _countries = new ObservableCollection<Models.Country>();

public ObservableCollection<Models.Country> Country
{
    get { return _countries; }
}

and the model:

public class Country
{
    public string Name { get; set; }
    public List<League> League { get; set; }
}

public class League
{
    public string Name { get; set; }
    public List<Event> Event { get; set; }
}

the class Event contains the properties of each event, in particular the name of the event, the date and so on..

I valorize this data as follows:

Country country = new Country();
country.Name = "Italy";

League league = new League();
league.Name = "Serie A";
League league2 = new League();
league2.Name = "Serie B";

Event @event = new Event();
@event.MatchHome = "Inter";

Event event2 = new Event();
@event.MatchHome = "Milan";

league.Event = new List<Event>(); 
league2.Event = new List<Event>();

league.Event.Add(@event);
league2.Event.Add(event2);

country.League = new List<League>();

country.League.Add(league);
country.League.Add(league2);

lsVm.Country.Add(country); //lsVm contains the ViewModel

How you can see I create an object called country (Italy) that will contains in this case two leagues (Serie A) and (Serie B). Each league contains one match actually in playing Serie A -> Inter and Serie B -> Milan

I add the league two the country, and finally the country to the observable collection in the viewmodel. Until here no problem. The problem's come in the xaml.

So I've organized all of this stuff inside of GroupViews, for doing this I'm using a CollectionViewSource, in particular:

<CollectionViewSource Source="{Binding Country}" x:Key="GroupedItems">
       <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="Name" />
            <PropertyGroupDescription PropertyName="League.Name" />
       </CollectionViewSource.GroupDescriptions>

the code above is located in my Window.Resources, and tell to CollectionViewSource to organize for country name and leagues name the respective leagues associated. I've two ListView as this:

<ListView ItemsSource="{Binding Source={StaticResource GroupedItems}}" Name="Playing">
                    <ListView.View>
                        <GridView>
                            <GridViewColumn Header="Date" Width="150" DisplayMemberBinding="{Binding Path = League.Event.MatchDate}"/>
                            <GridViewColumn Header="Minutes" Width="70" DisplayMemberBinding="{Binding Path = League.Event.MatchMinute}"/>
                            <GridViewColumn Header="Home" Width="150" DisplayMemberBinding="{Binding Path = League.Event.MatchHome}"/>
                            <GridViewColumn Header="Score" Width="100" DisplayMemberBinding="{Binding Path = League.Event.MatchScore}"/>
                            <GridViewColumn Header="Away" Width="150" DisplayMemberBinding="{Binding Path = League.Event.MatchAway}"/>
                        </GridView>
                    </ListView.View>
<GroupStyle>
                            <GroupStyle.ContainerStyle>
                                <Style TargetType="{x:Type GroupItem}">
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate>
                                                <Expander IsExpanded="True" Background="#4F4F4F" >
                                                    <Expander.Header>
                                                        <StackPanel Orientation="Horizontal" Height="22">
                                                            <TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="White" FontSize="22" VerticalAlignment="Bottom" />
                                                            <TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Orange" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
                                                            <TextBlock Text=" Leagues" FontSize="22" Foreground="White" FontStyle="Italic" VerticalAlignment="Bottom" />
                                                        </StackPanel>
                                                    </Expander.Header>
                                                    <ItemsPresenter />
                                                </Expander>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </GroupStyle.ContainerStyle>
                        </GroupStyle>

The GroupStyle contains the leagues that will contains each match, now the problem is that I can't see any league and any match 'cause this item are inside of a list. So for display them I should write in the xaml this code:

<PropertyGroupDescription PropertyName="League[0].Name" /> 

this fix the bug of the league name displayed into GroupStyle and in the GridView:

<GridViewColumn Header="Casa" Width="150" DisplayMemberBinding="{Binding Path = League[0].Event[0].MatchHome}"/>

but this of course will display only the specific item.. not the list of items. I need help to fix this situation, I cannot figure out. Thanks.

Upvotes: 0

Views: 958

Answers (1)

Eli Arbel
Eli Arbel

Reputation: 22739

If you want to use the ListView's grouping abilities, you have to provide it a flat list of the items you want to group (in your case, the leagues), not the header items. The CollectionView does the grouping for you by specifying GroupDescriptions.

For example, assuming the League class has a Country property:

class ViewModel
{
    public ObservableCollection<Models.Country> Country { get; }

    public IEnumerable<League> AllLeagues => Country.SelectMany(c => c.Leagues);
}

public class League
{
    public string Name { get; set; }
    public List<Event> Event { get; set; }
    // add Country here
    public Country Country { get; set; }
}

class 

<CollectionViewSource Source="{Binding AllLeagues}" x:Key="GroupedItems">
   <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="Country" />
   </CollectionViewSource.GroupDescriptions>

Then when you bind the columns, you bind directly to League properties, e.g.:

<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Path=Event.MatchDate}"/>

And in the group style you can bind to Country properties, as you've done.

Alternative solution

If you want to display any hierarchical data in WPF you can either use a control that was built for it (such as the Xceed data grid) or hack it together with the built-in WPF data grid's row details.

Here's a sample XAML for this (note it uses your original data structures without the modifications I suggested above). These are essentially 3 data grids nested within each other. Each grid has its own set of columns, so you can define anything you want for each level (Country, League, Event).

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:app="clr-namespace:WpfApp"
        d:DataContext="{d:DesignData ViewModel}">
    <FrameworkElement.Resources>
        <app:VisibilityToBooleanConverter x:Key="VisibilityToBooleanConverter" />
        <DataTemplate x:Key="HeaderTemplate">
            <Expander IsExpanded="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridRow}, Path=DetailsVisibility, Converter={StaticResource VisibilityToBooleanConverter}}" />
        </DataTemplate>
        <Style x:Key="DataGridStyle"
               TargetType="DataGrid">
            <Setter Property="RowHeaderTemplate"
                    Value="{StaticResource HeaderTemplate}" />
            <Setter Property="RowDetailsVisibilityMode"
                    Value="Collapsed" />
            <Setter Property="AutoGenerateColumns"
                    Value="False" />
            <Setter Property="IsReadOnly"
                    Value="True" />
        </Style>
    </FrameworkElement.Resources>
    <Grid>
        <DataGrid ItemsSource="{Binding Country}"
                  Style="{StaticResource DataGridStyle}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name"
                                    Binding="{Binding Name}" />
            </DataGrid.Columns>
            <DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    <DataGrid ItemsSource="{Binding League}"
                              Style="{StaticResource DataGridStyle}">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="Name"
                                                Binding="{Binding Name}" />
                        </DataGrid.Columns>
                        <DataGrid.RowDetailsTemplate>
                            <DataTemplate>
                                <DataGrid ItemsSource="{Binding Event}"
                                          AutoGenerateColumns="False"
                                          IsReadOnly="True">
                                    <DataGrid.Columns>
                                        <DataGridTextColumn Header="Match Home"
                                                            Binding="{Binding MatchHome}" />
                                    </DataGrid.Columns>
                                </DataGrid>
                            </DataTemplate>
                        </DataGrid.RowDetailsTemplate>
                    </DataGrid>
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>
        </DataGrid>
    </Grid>
</Window>

You'll also need the code for the converter I used:

public class VisibilityToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => value as Visibility? == Visibility.Visible;

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        => value as bool? == true ? Visibility.Visible : Visibility.Collapsed;
}

Upvotes: 1

Related Questions