Jan 28, 2006

How to sort groups of data items

14SortingGroups

With the introduction of CollectionViewSource, we are now able to do basic grouping of data items in an ItemsControl without using code. In this post I will show you how to group items and sort those groups.

The data source of this sample consists of a list of objects of type Animal. Animal has a Name and a Category (which is an enumeration). I want to group the items depending on their Category. This is easily done in markup by using CollectionViewSource:

    <Window.Resources>
        <local:Animals x:Key="animals"/>

        <CollectionViewSource x:Key="cvs" Source="{Binding Source={StaticResource animals}, Path=AnimalList}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="Category"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>

        <DataTemplate x:Key="animalTemplate">
            <TextBlock Text="{Binding Path=Name}" Foreground="MediumSeaGreen"/>
        </DataTemplate>
    </Window.Resources>

    <ItemsControl ItemsSource="{Binding Source={StaticResource cvs}}" ItemTemplate="{StaticResource animalTemplate}"/>

As I explained in a previous post, CollectionViewSource creates a custom View over the source list through markup. A view is a layer on top of a source data list that allows us to group, sort, and filter items, as well as keep track of the currently selected item.

If you try the sample markup above, you will see the names of the animals, but no information about the groups. The next step is to provide a template to display the group titles. CollectionViewSource wraps each group of items in an object of type CollectionViewGroup, and we are interested in its “Name” property, which we can display using the following template:

    <DataTemplate x:Key="categoryTemplate">
        <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Foreground="ForestGreen" Margin="0,5,0,0"/>
    </DataTemplate>

In order to use this template for the group titles, we have to add it to the GroupStyle property of ItemsControl (which takes a collection of GroupStyle objects):

    <ItemsControl ItemsSource="{Binding Source={StaticResource cvs}}">
        <ItemsControl.GroupStyle>
            <GroupStyle HeaderTemplate="{StaticResource categoryTemplate}" />
        </ItemsControl.GroupStyle>
    </ItemsControl>

We could add more GroupStyles to the collection, in which case they would be applied to different levels of groups. (For simplicity, we just have one level of grouping in this sample.)

At this point, the groups and items display correctly, but we would like to sort the groups and the items within the groups. I’ve seen a few people approach this by looking for a specific “SortGroups” method or something similar. We didn’t design a special API to sort groups because you can accomplish that simply by sorting the items by the same property by which you are grouping:

    <CollectionViewSource x:Key="cvs" Source="{Binding Source={StaticResource animals}, Path=AnimalList}">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="Category"/>
        </CollectionViewSource.GroupDescriptions>
        <CollectionViewSource.SortDescriptions>
            <scm:SortDescription PropertyName="Category" />
            <scm:SortDescription PropertyName="Name" />
        </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>

Adding two sort descriptions allows us to sort the groups first and then the items within the groups. Notice that because Category is an enumeration, sorting by that property will display the groups in the order they are defined in the enumeration (which may or may not be alphabetically). Name is of type string, so the leaf items will be displayed alphabetically.

This is a screenshot of the completed sample:

Here you can find the VS project with this sample code. This works with January CTP WPF bits.

Update September 17, 2007: Here you can find this sample with Orcas Beta 2 bits.

33 Comments
  1. Pascal Bourque

    Hi Beatriz,

    In your GroupStyle’s HeaderTemplate (“categoryTemplate”), you bind a TextBlock’s Text property to Path=Name:

    <TextBlock Text=”{Binding Path=Name}” … />

    I assume this is the “Name” property on some Group definition class, but I can’t find any doc for it…

    In other words, my question is: what is the data type of the DataContext of a DataTemplate that is used as a Group’s HeaderTemplate? And what properties other than “Name” do I have access to regarding the current group?

    Thanks! :-)

    • Bea

      CollectionViewSource wraps each group in a CollectionViewGroup object. The Name property of CollectionViewGroup returns the Name of the group (notice that this is of type object, not string).

      There are a few other interesting properties in CollectionViewGroup, such as the Items property that returns the items within that group. There is also an ItemCount property that tells us how many items there are in the group. The bool IsBottomLevel property gives us information about whether the item is a leaf. You can easily look up “CollectionViewGroup” to find more complete information of everything in contains.

      Let me know if this helped.

      • Pascal Bourque

        Thanks for the info! It does help! :-)

  2. jestin r

    Hi,
    Your sample is really interesting but my case is a little complex.
    In fact I’m making an album photos with an XML file (databinding). Here an example of the XML source:
    < Application >
    < Albums >
    < Album name="Default" auteur="default">
    < Photo>
    < Name >titi< / Name >
    < Image >D:WallpaperAs_cold_as_silence_2.jpg< / Image >
    < / Photo >
    < / Album >
    < Album name="test" auteur="Nous" >

    < / Album >
    < / Albums >
    < / Application >
    I have two listbox in a grid (which contains the datacontext XPath=/Application/Albums/Album).
    One of the two contains the list of albums’ name and the other one the list of photos of the album selected in the previous one. By using dataTemplate I succeeded in doing this but I want to integrate sort. The problem is that when I follow your sample my list of photos is sorted (and contains all photos of all albums (first problem)) but when I want to changed the album (by clicking on the ohter listbox) that don’t switch (second problem). And that normal because the source of the “ItemSource” of the list of photos is the static CollectionViewSource which is XML source. Here the part of the code:
    < CollectionViewSource x:Key="NameView" Source="{Binding Source={StaticResource PhotosXMLSource}, XPath=/Application/Albums/Album/Photo}" > < CollectionViewSource.SortDescriptions > < cm:SortDescription PropertyName="Name" Direction="Ascending" /> < /CollectionViewSource.SortDescriptions > < / CollectionViewSource >

    So I need to integrate my Template in the CollectionViewSource. But I don’t succeed in doing this:
    < CollectionViewSource x:Key="NameView" Source="{Binding Source={StaticResource TemplatePhoto}" > It doesn’t find data and doesn’t display my photos.
    My listbox has this code: ItemTemplate=”{DynamicResource PhotoTemplate}” ItemsSource=”{Binding Source={StaticResource NameView}}”
    and my template :
    …< TextBlock Text=”{Binding Mode=OneWay, XPath=Name}”…

    Can you help me?

    Sincerely.

    • Bea

      Hi Jestin,

      If I understand correctly, you’re trying to have both the names of the albums and the photos for each album sorted, right?

      If so, you just encountered a very interesting scenario. I was able to solve your problem by using only XAML, but I have to admit it was a little tricky.

      I started by using a CollectionViewSource to sort the first ListBox, with a SortDescription with property set to @name. So far so good, the tricky part is to get the second ListBox sorted.

      If you add a second CollectionViewSource with the same source as the first one, a second view is created on top of the collection and master detail will break. I assume this is what was going on in your attempt, since you mentioned that the second ListBox is not in sync with the first one anymore, and was displaying all items.

      So, one solution I found to this problem was to add a second CollectionViewSource bound to the first one, and add sorting there. In this case, the sort property is Name. Then I bound the second ListBox to this CollectionViewSource.

      This seems to work pretty well. You can find my source code here.

      Thanks,
      Bea

  3. Owen

    Is there a way to make the Group Items selectable without ripping out and replacing the innards of ListView?

  4. Paul

    Great stuff!

    I was wondering how you might sort a collection view by ItemCount? I try adding that as a sort description and it doesn’t seem to work.

    Thanks,

    paul

  5. Jan Kučera

    Hello,
    I’d like to ask, how can I sort the groups in non-alphabetical order, eg to use custom comparer or something like this.

    Thank you,
    Jan

  6. Suke

    Hi,

    Great info!.

    I’m trying to display my groups horizontally (instead of of vertically as in your example). I’ve tried to modify the groupstyle but I end up with a stack of the grouped data (same as your example).

    Thank you,

    Suke

  7. Bea

    Hi Owen,

    No, there is no way to make the group headers selectable, if you’re using any Control that derives from Selector (e.g ListBox or ListView). TreeView has a different selection model, and there you can select an item that has children.

    Bea

  8. Bea

    Hi Suke,

    You can display your groups horizontally by setting the Panel property of GroupStyle to a template with a horizontal Panel. If you modify my sample to include the following XAML, the groups will be displayed horizontally:

    <ItemsPanelTemplate x:Key=”horizontalPanel”>
    <StackPanel Orientation=”Horizontal” />
    </ItemsPanelTemplate>

    <ItemsControl ItemsSource=”{Binding Source={StaticResource cvs}}”
    Width=”600″ ItemTemplate=”{StaticResource animalTemplate}”>
    <ItemsControl.GroupStyle>
    <GroupStyle HeaderTemplate=”{StaticResource categoryTemplate}” Panel=”{StaticResource horizontalPanel}”/>
    </ItemsControl.GroupStyle>
    </ItemsControl>

    If you wanted the actual items to be displayed horizontally, you would have to set the ItemsPanel property of ItemsControl to the same template.
    Here you can find a project with this XAML.

    Let me know if this helps.

    Bea

  9. Bea

    Hi Jan,

    Yes, you can certainly do custom sorting. I actually blogged about custom sorting in this blog post.

    You can also have custom groups - you can write some code that determines how you want your data to be grouped. If you’re curious about custom grouping, you can look at this sample I just uploaded.

    Hope this helped.

    Bea

  10. Bea

    Hi Paul,

    That is a really interesting scenario! Unfortunately our current feature set does not support that. We have ways to control the order of the groups if you know them ahead of time, but that does not solve your problem.

    The only way to support that would be to add a “group sorting” feature. If we did that, the order of the events would be: item sorting -> generation of groups -> group sorting. I’ve added this request to our feature list. This is the first time that I hear someome asking for this feature - if other readers feel that this would be a good feature to add, please leave me a comment. We typically decide the next set of features based on how much customers want them.

    Thanks for your comment!
    Bea

  11. Topaz

    Hello Bea,
    Thanks for your time and knowledge sharing.
    I wanted to ask you also about grouping.
    I want to build a horizontal stack Panel just like in the previous example (asked by Suke)
    and for each group in the CollectionViewSource I want to add a list box. Items can be added during runtime and thus new groups can be created (groups that need to be sorted - the list boxes need to be sorted)
    Want are the necessary steps in order to accomplish this.

    Thanks

    Topaz

    • Bea

      Hi Topaz,

      I wrote some code for you, which you can find here. Let me know if this is what you want.

      I started by adding a ListBox to the template for the groups, and bound that ListBox to the items for that group:

      <DataTemplate x:Key=”categoryTemplate”>
      <StackPanel>
      <TextBlock Text=”{Binding Path=Name}” FontWeight=”Bold” Foreground=”ForestGreen” Margin=”0,5,0,0″/>
      <ListBox ItemsSource=”{Binding Path=Items}” ItemTemplate=”{StaticResource animalTemplate}”/>
      </StackPanel>
      </DataTemplate>

      Then I set the ItemTemplate of the main ItemsControl to an empty DataTemplate. That’s all I had to do!

      If you add new items they will be added to the UI, and if the addition of new items generates new groups, they will also be added automatically.

      I hope this helps!

      Bea

      • Topaz

        Bea,
        your full scale explanationexample helped me a lot!!

        Thank you very much.
        Topaz

        • Bea

          Hi Topaz,

          I’m glad it was useful.

          Thanks for reading my blog!
          Bea

  12. Udi

    Hi,

    Im trying to create a treeview with a source of a Dictionary that is created by codebehind.

    dictionary is built with

    where ExtendedUser is made from:

    enum status
    User user (it is an imported type that i receive from a third party)

    i would like a tree view that binds to that collection and group by status while showing extendedUser.User.Name.

    how do i perform this?
    Thanks!
    Udi.

    • Bea

      Hi Udi,

      It seems that there were some issues with the formatting of your question. Maybe you inserted “<” and “>” tags as part of your XAML code directly? I believe these characters have to be escaped in the comment.

      If you post again, I will take a look at your problem.

      Thanks,
      Bea

  13. Muffadal

    Hi there,

    Thanks for the gr8 post. I am trying to do dynamic grouping. I have a listbox containing items and the items by which they are grouped is null by default. Only after a cretain event like a button click the property is set. Similar to “group” “ungroup” feature with most diagramming sofwares where there item on the canavs can be grouped togather. I hope u got my problem.

    Thanks,

    Muffadal.

    • Bea

      Hi Muffadal,

      It seems like you want to add and remove grouping when the user clicks a button. This can be done by adding and removing group descriptions programmatically. You can get the default view for your collection with the CollectionViewSource.GetDefaultView(…) method. Once you have the view, you can change its GroupDescriptions collection directly.

      Hope this helps.

      Bea

  14. steve

    any source code on multisorting a list view the is boung to a observable collection

  15. Bjørnar

    Hi
    Excellent posts. They have been great help to me on several occasions.

    My problem is similar to what Jan experienced. I need to group, and sort the groups in non-alphabetical and non-enum value order. I also need to be able to sort by clicking column headers. When clicking the headers, I add a SortingDescription with the property/binding to sort by.

    From some of the posts I have read here, sorting groups has been performed by adding a SortingDescription for grouping, and then another for items within a group. I can’t use SortingDescription for sorting the groups, as the enum values are not in the order I want to sort by, and I also use a converter to localize the values of an enum to a proper user-readable text.

    Tweaking with the CustomSort property seems to work for the groups, however, when adding a SortDescription after a column header click, the group order is no longer in effect as it seems to clear CustomSort (not documented). Setting CustomSort clears SortDescriptions (documented)

    Thanks.

    /B

    • Bjørnar

      I figured out one solution. Use GroupDescription and SortDescription as normal, and add GroupNames in the order I want. Then modify the GroupItem template to have Visibility=Collapsed if there are no items in the group.

      • Bea

        Hi Bjørnar,

        Sorry I didn’t have a chance to get to your question earlier. I’m glad you found a solution.

        Bea

  16. Vanja

    Hi Bea,

    I was wondering if there is a way to insert a converter in the SortDescription? I have a GroupDescription created from an IEnumerable (with a converter) and I would like the grouping to be sorted (ie. Group A always before Group Z). Is there a way to extend the SortDescription class to allow a Converter?

    I’d also like to know if its possible to delay grouping calculation or force a recalculation? I am using a CollectionViewSource on my ViewModel collection, but the grouped property is lazy loaded internally. I have found that when the real value is finally loaded the grouping is not updated. Can I force a recalculation?

    Thanks for the help,

    Vanja

  17. Alexd

    Hi Bea
    Thanks for the great blog posts. i always find answers to my WPF questions here.

    Regarding the collection groupings, is it possible to have items that do not belong to a group ? In my case I use an expander for the categoryTemplate allowing me to expand/collapse the groups. I would like that groups with a single element in them don’t have an expander associated (so they won’t show the group element). Is this even possible?

    Alex.

  18. DavidO

    Hi Bea,

    This is somewhat related to your post above…I have 2 problems and I’m not sure what is causing them. I have explained them here http://forums.silverlight.net/forums/t/140407.aspx
    From reading your blog I imagine you would well able to explain what is happening.

    I hope you have the time to look!

    DavidO

  19. Bea

    It seems like someone has already provided a complete solution to your problem.

  20. Bea

    Hi Alex,

    Once you opt to have grouping, all your items will belong to some group - you can’t have “ungrouped” items. You can accomplish what you describe by providing different styles for the groups depending on the number of items within the group. Within the template for the group header, you can have a binding with Path=Items.Count to get the number of items within that group. Then you can use a trigger to control the look of the template (e.g. you may control the visibility of sections of your template).

    Bea

  21. Bea

    Hi Vanja,

    It seems to me that you’re looking for custom sorting. I have a blog post that explains how you can do that.

    Yes, it’s possible to recalculate/refresh the view. If you’re using CollectionViewSource, you can do that using cvs.View.Refresh().

    Bea

  22. Kit West

    For others who try to use this info …
    xmlns:scm=”clr-namespace:System.ComponentModel;assembly=WindowsBase”

Comments are closed.