AgainMe AgainMe - 3 months ago 17
C# Question

Flatten groups in CollectionViewSource

I need to count the leagues available inside an

ObservableCollection
. The problem's that the league name is duplicated, so I need to remove the duplicates. Actually what I've is this:

private static ObservableCollection<Models.Event> _matches = new ObservableCollection<Models.Event>();

public ObservableCollection<Models.Event> Matches
{
get { return _matches; }
}


Essentially I've a collection of events, now each event have a league associated. My idea is create another property called
LeaguesCount
:

public int LeaguesCount
{
//remove duplicate record from _matches here
}


and return the leagues available, but there is another problem. Because each league is different, so I could have:

Premier League x3
Serie A x5
Super League x4
...


I need to get the count numer separated. Noticed that this league are associate in a nation name, so I could have in
_matches
two event with this structure:

Event (1)

match_name = 'Foo';
match_nation = 'England';
match_league = 'Premier League';


Event (2)

match_name = 'Test';
match_nation = 'England';
match_league = 'Premier League';


Event (3)

match_name = 'Another event';
match_nation = 'England';
match_league = 'Championship';


so how you can see in
England
nation are available three events with
Premier League
leagues name and
Championship
league name, I need to get from nation
England
the count of all leagues avoid of course the duplicate.

The structure of the event is basically this:

public class Event : ViewModel
{
private string _matchNation,
_matchLeague,
_matchName;
}


Each fields have a property associated. The properties related to this question are:

public string MatchNation
{
get { return _matchNation; }
set
{
_matchNation = value;
OnPropertyChanged();
}
}
public string MatchLeague
{
get { return _matchLeague; }
set
{
_matchLeague = value;
OnPropertyChanged();
}
}
public string MatchName
{
get { return _matchName; }
set
{
_matchName = value;
OnPropertyChanged();
}
}


Result wanted:

England -> League count (2) [Championship, Premier League]


The main problem is the different nation name, because I bind the source in the xaml like this:

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


and in the
GroupHeaderTemplate
:

<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" />


but the
ItemCount
actually return all the items count (event) and not the leagues without duplicated, that's an error.

Answer

You gave me really a little bit headache on this. Unfortunately, your Models are not in the shape to do this. I've modified them a bit (I was too lazy to implement INotifyPropertyChanged everywhere):

Models

 public class Country {

        public Country() {
            this.Leagues = new ObservableCollection<League>();
        }
        public string Name { get; set; }

        public ObservableCollection<League> Leagues { get; }

    }

    public class League {

        public League() {
            this.Events = new ObservableCollection<Event>();
        }
        public string Name { get; set; }

        public ObservableCollection<Event> Events { get; }
    }

    public class Event : INotifyPropertyChanged {
        private string _matchName;
        public string MatchName {
            get { return _matchName; }
            set {
                _matchName = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        }
    }

ViewModel with Demo-Data

public partial class VM {

        public VM() {
            var eng = new Country() {Name = "England"};
            var l1 = new League() {Name = "Championship" };
            l1.Events.Add(new Event() {MatchName = "Another event" });
            var l2 = new League() { Name = "Premier League" };
            l2.Events.Add(new Event() { MatchName = "Foo" });
            l2.Events.Add(new Event() { MatchName = "Bar" });
            eng.Leagues.Add(l1);
            eng.Leagues.Add(l2);
            this.Countries.Add(eng);

        }

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

        public ObservableCollection<Country> Countries {
            get { return _countries; }
        }

    }

CollectionViewSource

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

ListView

<ListView ItemsSource="{Binding Source={StaticResource GroupedItems}}">
        <ListView.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <WrapPanel DataContext="{Binding Items}">
                            <TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="22" VerticalAlignment="Bottom" />
                            <TextBlock Text="->" Margin="10,0,0,0" FontWeight="Bold" FontSize="22" VerticalAlignment="Bottom"/>
                            <TextBlock Text="League count" Margin="10,0,0,0" FontWeight="Bold" FontSize="22" VerticalAlignment="Bottom"/>
                            <TextBlock Text="{Binding Leagues.Count}" FontWeight="Bold" FontSize="22" VerticalAlignment="Bottom" Margin="10,0,0,0" />
                            <ItemsControl ItemsSource="{Binding Leagues}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <TextBlock Text="{Binding Name}" FontSize="22" Foreground="Orange" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <WrapPanel></WrapPanel>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>
                        </WrapPanel>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListView.GroupStyle>
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel DataContext="{Binding Leagues}">
                    <ItemsControl ItemsSource="{Binding}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Name}"></TextBlock>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>

                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

Result

Result

Note

As you can see, the Items displayed below your Groupheader can be modified (Marked in Yellow). To do this, you have to change The DataContext of the Panels each time (Im quite sure, there is no other way).

Closure

Not pretty but working. You may have to play around with the XAML so it fits your needs