Grouping is crazy easy in WPF

Lots of people struggle with grouping data and when they do, the brut force method normally takes over and we start creating some crazy super structure to organize data and then do something like put a List inside of a List or a Grid inside of a list.  Just think of how many times you tried to put a GridView inside of a Repeater back when we didn’t know about jQuery or MVC and WebForms were all we knew.

Well it turns out WPF (and Silverlight 4.0) make this crazy easy.  It’s why the CollectionViewSource class exists.  The thing people don’t realize is that you don’t have to change anything about your data structure to make this work well (most times) and your designer (you have one right?) can make the decision on how best to display the information.

 

Step 1: Get Yourself Some Data

Don’t go crazy trying to group your data in your view model with Linq statements or anything like that.  You don’t (likely) need it.  Just get a list of your Animals with the necessary attributes for display in normal IEnumerable form.

Here’s how I’m doing to get my data for the sample:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <XmlDataProvider x:Key="data">
            <x:XData>
                <Animals xmlns="">
                    <Animal name="Dory" Species="Fish" />
                    <Animal name="Felix" Species="Cat" />
                    <Animal name="Fluffy" Species="Dog" />
                    <Animal name="Jake" Species="Snake" />
                    <Animal name="Mittens" Species="Cat" />
                    <Animal name="Murtle" Species="Turtle" />
                    <Animal name="Nemo" Species="Fish" />
                    <Animal name="Rex" Species="Dog" />
                    <Animal name="Rover" Species="Dog" />
                    <Animal name="Toonces" Species="Cat" />
                </Animals>
            </x:XData>
        </XmlDataProvider>
    </Page.Resources>
</Page>
Step 2: Group The Data with CollectionViewSource

We need to remember that Xaml is just a way to instantiate classes.  Knowing that, we can create a CollectionViewSource right in our xaml like this:

 

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <XmlDataProvider x:Key="data">
            <x:XData>
                <Animals xmlns="">
                    <Animal name="Dory" Species="Fish" />
                    <Animal name="Felix" Species="Cat" />
                    <Animal name="Fluffy" Species="Dog" />
                    <Animal name="Jake" Species="Snake" />
                    <Animal name="Mittens" Species="Cat" />
                    <Animal name="Murtle" Species="Turtle" />
                    <Animal name="Nemo" Species="Fish" />
                    <Animal name="Rex" Species="Dog" />
                    <Animal name="Rover" Species="Dog" />
                    <Animal name="Toonces" Species="Cat" />
                </Animals>
            </x:XData>
        </XmlDataProvider>
        <CollectionViewSource x:Key="animalsBySpecies" Source="{Binding Source={StaticResource data}, XPath=Animals/Animal}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="@Species" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Page.Resources>
</Page>

 

Step 3: Show The Data

This is where the fun happens.  We take something as simple as a ListView or, simplier yet, the ItemsControl and we hook it up to our CollectionViewSource like this:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <XmlDataProvider x:Key="data">
            <x:XData>
                <Animals xmlns="">
                    <Animal name="Dory" Species="Fish" />
                    <Animal name="Felix" Species="Cat" />
                    <Animal name="Fluffy" Species="Dog" />
                    <Animal name="Jake" Species="Snake" />
                    <Animal name="Mittens" Species="Cat" />
                    <Animal name="Murtle" Species="Turtle" />
                    <Animal name="Nemo" Species="Fish" />
                    <Animal name="Rex" Species="Dog" />
                    <Animal name="Rover" Species="Dog" />
                    <Animal name="Toonces" Species="Cat" />
                </Animals>
            </x:XData>
        </XmlDataProvider>
        <CollectionViewSource x:Key="animalsBySpecies" Source="{Binding Source={StaticResource data}, XPath=Animals/Animal}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="@Species" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Page.Resources>
    <DockPanel>
        <ScrollViewer DockPanel.Dock="Bottom" VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource="{Binding Source={StaticResource animalsBySpecies}}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding XPath=@name}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </DockPanel>
</Page>


This doesn’t give us anything we couldn’t do before:

SNAGHTML5b58fce

 

So how do we represent this grouping?

Step 3: Display the Grouping

Easy, we define the GroupStyle.  There is an awful lot we can do here, but we’ll go with putting each into a GroupBox with the Header being the Species.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <XmlDataProvider x:Key="data">
            <x:XData>
                <Animals xmlns="">
                    <Animal name="Dory" Species="Fish" />
                    <Animal name="Felix" Species="Cat" />
                    <Animal name="Fluffy" Species="Dog" />
                    <Animal name="Jake" Species="Snake" />
                    <Animal name="Mittens" Species="Cat" />
                    <Animal name="Murtle" Species="Turtle" />
                    <Animal name="Nemo" Species="Fish" />
                    <Animal name="Rex" Species="Dog" />
                    <Animal name="Rover" Species="Dog" />
                    <Animal name="Toonces" Species="Cat" />
                </Animals>
            </x:XData>
        </XmlDataProvider>
        <CollectionViewSource x:Key="animalsBySpecies" Source="{Binding Source={StaticResource data}, XPath=Animals/Animal}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="@Species" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Page.Resources>
    <DockPanel>
        <ScrollViewer DockPanel.Dock="Bottom" VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource="{Binding Source={StaticResource animalsBySpecies}}">
                <ItemsControl.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.ContainerStyle>
                            <Style TargetType="{x:Type GroupItem}">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="{x:Type GroupItem}">
                                            <GroupBox Header="{Binding Name}">
                                                <ItemsPresenter />
                                            </GroupBox>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </GroupStyle.ContainerStyle>
                    </GroupStyle>
                </ItemsControl.GroupStyle>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding XPath=@name}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </DockPanel>
</Page>


 

We’ve told WPF, take all of the Groups out of the CollectionViewSource and put each into a Container (just like it would with individual items), that container being a GroupItem instead of a ListItem.  Style the GroupItem such that its Template is a GroupBox with an ItemsPresenter inside to display each individual item.  You can do a lot with this and it also works with the GridView form of a ListView.

SNAGHTML5b5f65d

  • Alex

    This is excellent, thanks for your help…
    I have a question…!
    Can you control the style of the group if you have more than 1 group?
    Example: If you added a Gender field to the grouping…
    Could you have Species in Bold, font size 20 and Gender in Italics, font size 12…?

  • Rick O’Shay

    All the accolades for crazy-easy make it all the more irritating that this obvious problem is not addressed in Silverlight. In fact, why even mention jQuery and MVC as WPF is not in that space, and the technology that is does not support grouping.

  • http://blog.cromwellhaus.com ryan

    Silverlight not supporting many of the little things in WPF is quite irritating, I agree.

    As for the reference to Repeaters and jQuery, I’m simply making an analogy to the things we found difficult in WebForms world that have been eased with jQuery. Similarly many things that were difficult with WinForms are now very easy with WPF (and often Silverlight :))

  • Artiom

    Well done, well done.

  • Alexander Schultz

    Thanks, this is exactly what I was looking for!

  • Aaron

    How about multiple groupings where each group has its own data template? I can’t find a way to make it work…

    • SilverX

      I would use GroupStyleSelector property, it works like a TemplateSelector

  • Stirredo

    I just signed up to say this:

    That yellow hightlighting, Please change it. It burns my eyes.

    Nice article, though.

  • TomerBu

    Thanks! Great tutorial.

  • Ravi

    Thanks

    • cromwellryan

      NP – thanks for the comment :)

  • Arnaud Weil

    Love the way you write your article, you make it cristal clear and easy to follow. Plus love the instanciation from whithin the XAML. I think you saved me some time fighting with CollectionViewSource.

  • Fredrik Forssén

    Fantastic article! Though the formatting makes it hard to read with all the vertical scrolling :/