Confessions of a ListBox groupie [Using IValueConverter to create a grouped list of items simply and flexibly]

This blog has moved to a new location and comments have been disabled.

All old posts, new posts, and comments can be found on The blog of

See you there!

Comments (50)
  1. cromwellryan says:

    Why couldn’t you do this:

    <Page xmlns="; xmlns:x=""&gt;


         <XmlDataProvider x:Key="data">


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






         <ScrollViewer DockPanel.Dock="Bottom" VerticalScrollBarVisibility="Auto">


             <CollectionViewSource x:Key="animalsBySpecies" Source="{Binding Source={StaticResource data}, XPath=Animals/Animal}">


                 <PropertyGroupDescription PropertyName="@Species" />




           <ItemsControl ItemsSource="{Binding Source={StaticResource animalsBySpecies}}">




                     <Style TargetType="{x:Type GroupItem}">

                       <Setter Property="Template">


                           <ControlTemplate TargetType="{x:Type GroupItem}">

                             <GroupBox Header="{Binding Name}">

                               <ItemsPresenter />











                 <TextBlock Text="{Binding XPath=@name}" />







  2. David Anson says:

    Because that doesn’t work in Silverlight. 🙂 The ItemsControl.GroupStyle property that forms the basis for the example above is not supported by any version of Silverlight (yet). (Neither is XmlDataProvider, as far as I know.)

    But I think what you were *really* suggesting was to use that technique for WPF in response to the challenge at the end of my post. In which case I agree with you! 🙂 That’s a really nice demonstration of some powerful platform support and it’s extra cool that it’s a XAML-only solution.

    Thanks a lot for sharing!

  3. Mustang65 says:

    This is great.  I have had many requests for this type of feature in a listbox.  The only thing I have to figure out now is how to collapse a group header.  Something similar to the datalist.

  4. cromwellryan says:

    I’m at fault for skimming… my apologies.

    GroupStyle does not seem to be on the SL 4 list either which is disappointing.

  5. David Anson says:


    At a certain point, it becomes easier to stop hacking around limitations and switch to a control that does what you need by its very design. 🙂 I’m not sure if you’re there yet or why it is that you need to use ListBox, but maybe it’s worthwhile to reconsider whether DataGrid could meet your needs – or whether the above WPF-specific solution would (because I think you could do collapse with it fairly easily).

    Thanks for your comment!

  6. David Anson says:


    No need to apologize – yours is a great solution on WPF!

  7. Mustang65 says:

    I currently have 2 different views of the same data.  Once view contains a listbox and a dataform, a Master Detail type view.  The other is a datalist view that also allows users to export the data.  Both the listbox and the datalist use the same RIA DomainDataSource.  The DDS has a filter on it which works for the datalist and does somewhat work for the listbox.  By that I mean the data list listed the same way just no collapsable headers.  The listbox is used because the way i am showing the data i dont require or need a column header and have a data template that shows a server name and then under that a current status and location.

  8. David Anson says:


    I think you can get rid of the headers on DataGrid and I’m pretty sure you could still use your DataTemplate if you switched over. It might be a little bit of work to do things under a slightly different programming model, but you’d get properr grouping and collapse for free.

    Of course, you know your situation far better than I do – I’m just thinking that you might be happier in the long run with a more officially supported approach. 🙂

    Hope this helps!

  9. DragAndDrop says:

    Drag and Drop with ListBoxDragDropTarget dont works

  10. David Anson says:


    Please have a look at the following post and follow up with the author if you still have questions. It can be a little tricky to set up, but it should work okay in most cases:…/new-with-silverlight-toolkit-drag-and.html

    FYI, there's an example of ListBoxDragDropTarget in the Toolkit samples project you can try online and look at the source code for:…/default.html

  11. Michael Bakker 1 says:

    You made my day with this very inventive solution! Thanks a lot for that!

    I translated the code to VB  to incorporate it into my silverlight controls library.

    Here it is for other VB-minded people…

    Imports System.Windows.Data

    ''' <summary>

    ''' Class that implements simple grouping for ItemsControl and its subclasses (ex: ListBox)

    ''' </summary>

    Public Class GroupingItemsControlConverter

       Implements IValueConverter

       ''' <summary>

       ''' Modifies the source data before passing it to the target for display in the UI.

       ''' </summary>

       ''' <param name="value">The source data being passed to the target.</param>

       ''' <param name="targetType">The Type of data expected by the target dependency property.</param>

       ''' <param name="parameter">An optional parameter to be used in the converter logic.</param>

       ''' <param name="culture">The culture of the conversion.</param>

       ''' <returns>The value to be passed to the target dependency property.</returns>

       Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert

           Dim valueAsIEnumerable As System.Collections.IEnumerable = CType(value, System.Collections.IEnumerable)

           Dim parameterAsGroupingItemsControlConverterParameter As GroupingItemsControlConverterParameters = CType(parameter, GroupingItemsControlConverterParameters)

           ' Validate parameters

           If valueAsIEnumerable Is Nothing Then

               Throw New ArgumentException("GroupingItemsControlConverter works for only IEnumerable inputs.", "value")

           End If

           If parameterAsGroupingItemsControlConverterParameter Is Nothing Then

               Throw New ArgumentException("Missing required GroupingItemsControlConverterParameter.", "parameter")

           End If

           Dim groupSelectorAsIGroupingItemsControlConverterSelector As IGroupingItemsControlConverterSelector = CType(parameterAsGroupingItemsControlConverterParameter.GroupSelector, IGroupingItemsControlConverterSelector)

           If groupSelectorAsIGroupingItemsControlConverterSelector Is Nothing Then

               Throw New ArgumentException("GroupingItemsControlConverterParameter.GroupSelector must be non-null and implement IGroupingItemsControlConverterSelector.", "parameter")

           End If

           ' Return the grouped results

           Return ConvertAndGroupSequence(valueAsIEnumerable.Cast(Of Object)(), parameterAsGroupingItemsControlConverterParameter)

       End Function

       Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack

           Throw New NotImplementedException()

       End Function

       ''' <summary>

       ''' Converts and groups the values of the specified sequence according to the settings of the specified parameters.

       ''' </summary>

       ''' <param name="sequence">Sequence of items.</param>

       ''' <param name="parameters">Parameters for the grouping operation.</param>

       ''' <returns>Converted and grouped sequence.</returns>

       Private Function ConvertAndGroupSequence(ByVal sequence As IEnumerable(Of Object), ByVal parameters As GroupingItemsControlConverterParameters) As IEnumerable(Of Object)

           ' Validate parameters

           Dim groupSelector As Func(Of Object, IComparable) = CType(parameters.GroupSelector, IGroupingItemsControlConverterSelector).GetGroupSelector()

           Dim lstObjects As New List(Of Object)

           If groupSelector Is Nothing Then

               Throw New NotSupportedException("IGroupingItemsControlConverterSelector.GetGroupSelector must return a non-null value.")

           End If

           ' Do the grouping and ordering

           Dim groupedOrderedSequence = sequence.GroupBy(groupSelector).OrderBy(Function(g) g.Key)

           ' Return the wrapped results

           For Each group In groupedOrderedSequence

               lstObjects.Add(New ContentControl With {.Content = group.Key, .ContentTemplate = parameters.GroupHeaderTemplate})

               For Each item In group

                   lstObjects.Add(New ContentControl With {.Content = item, .ContentTemplate = parameters.ItemTemplate})



           Return lstObjects

       End Function

    End Class

    ''' <summary>

    ''' Class that represents the input parameters to the GroupingItemsControlConverter class.

    ''' </summary>

    Public Class GroupingItemsControlConverterParameters

       Private m_GroupHeaderTemplate As DataTemplate

       Private m_ItemTemplate As DataTemplate

       Private m_GroupSelector As IGroupingItemsControlConverterSelector

       ''' <summary>

       ''' Template to use for the header for a group.

       ''' </summary>

       Public Property GroupHeaderTemplate As DataTemplate


               Return m_GroupHeaderTemplate

           End Get

           Set(ByVal value As DataTemplate)

               m_GroupHeaderTemplate = value

           End Set

       End Property

       ''' <summary>

       ''' Template to use for the items of a group.

       ''' </summary>

       Public Property ItemTemplate As DataTemplate


               Return m_ItemTemplate

           End Get

           Set(ByVal value As DataTemplate)

               m_ItemTemplate = value

           End Set

       End Property

       ''' <summary>

       ''' Selector to use for determining the grouping of the sequence.

       ''' </summary>

       Public Property GroupSelector As IGroupingItemsControlConverterSelector


               Return m_GroupSelector

           End Get

           Set(ByVal value As IGroupingItemsControlConverterSelector)

               m_GroupSelector = value

           End Set

       End Property

    End Class

    ''' <summary>

    ''' Interface for classes to be used as a selector for the GroupingItemsControlConverterParameters class.

    ''' </summary>

    Public Interface IGroupingItemsControlConverterSelector

       ''' <summary>

       ''' Function that returns the group selector.

       ''' </summary>

       ''' <returns>Key to use for grouping.</returns>

       ReadOnly Property GetGroupSelector As Func(Of Object, IComparable)

    End Interface

  12. David Anson says:

    Michael Bakker 1,

    Thanks for the assistance! 🙂

  13. KCS says:

    Hi. I got this to work – but in VS2010 design mode (with SL4) it only displays once, then I get this error from then on. Can you help? My data context is a viewmodel and my collection is a generic list of a basic class.

    GroupingItemsControlConverter works for only IEnumerable inputs.

    Parameter name: value

      at ValueConverters.GroupingItemsControlValueConverter.Convert(Object value, Type targetType, Object parameter, CultureInfo culture) in C:ProjectsInspireValueConvertersGroupingItemsControlValueConverter.cs:line 31

      at System.Windows.Data.BindingExpression.ConvertToTarget(Object value)

      at System.Windows.Data.BindingExpression.GetValue(DependencyObject d, DependencyProperty dp)

      at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet)

      at System.Windows.Data.BindingOperations.SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding)

      at Microsoft.Expression.Platform.Silverlight.SilverlightDependencyPropertyImplementation.SetBinding(Object target, Object value)

      at Microsoft.Expression.DesignModel.Core.InstanceBuilderOperations.SetValue(Object target, IProperty propertyKey, Object value)

      at Microsoft.Expression.DesignModel.InstanceBuilders.ClrObjectInstanceBuilder.ModifyValue(IInstanceBuilderContext context, ViewNode target, IProperty propertyKey, Object value, PropertyModification modification)

      at Microsoft.Expression.DesignModel.InstanceBuilders.DependencyObjectInstanceBuilderBase`1.ModifyValue(IInstanceBuilderContext context, ViewNode target, IProperty propertyKey, Object value, PropertyModification modification)

      at Microsoft.Expression.Platform.Silverlight.InstanceBuilders.DependencyObjectInstanceBuilder.ModifyValue(IInstanceBuilderContext context, ViewNode target, IProperty propertyKey, Object value, PropertyModification modification)

      at Microsoft.Expression.Platform.Silverlight.InstanceBuilders.FrameworkElementInstanceBuilder.ModifyValue(IInstanceBuilderContext context, ViewNode target, IProperty propertyKey, Object value, PropertyModification modification)

      at Microsoft.Expression.DesignModel.InstanceBuilders.ClrObjectInstanceBuilder.UpdateProperty(IInstanceBuilderContext context, ViewNode viewNode, IProperty propertyKey, DocumentNode valueNode)

  14. David Anson says:


    It seems like whatever's getting passed into the Convert method the second time by the VS designer isn't an IEnumerable. I might start by adding some debug-time exceptions there to give a little more information: What IS getting passed in? What's its type? What if we ignore the bad type the first time – do we get a good type the next time? Given that it's working for you at run-time, I'm optimistic that this problem can be fairly easily solved once we understand what's special about the second display at design time. 🙂

    Hope this helps!

  15. Sort says:

    Hello, can you elaborate on how to sort the items in the group via Name?

  16. John Zhu says:


    Right now each item is outputted like this:



                           <ContentControl Content="{Binding Name}"/>



    However, this only works for single line. If there are multiple lines of data, and a stack panel of controls is used, this will break the <local:IsAnimalConverter x:Key="IsAnimalConverter"/> because each control's content will be an Animal. What's the recommended way around that?




                                               <StackPanel Margin="0,0,0,17" Width="432">

                                                   <TextBlock Text="{Binding Name}" />

                                                   <TextBlock Text="{Binding Description}" />





  17. David Anson says:


    If I understand your scenario, you want to sort the items within each group as well as having the groups sorted. One approach is to sort the complete list before-hand and rely on the fact that GroupBy maintains the original ordering within each group to give you sorted groups with no code changes required. That's what I've done in my example by having the original list of animals already sorted by name. However, if that's not practical for your scenario, you'd probably want to modify the Linq statement below the comment "// Do the grouping and ordering" to either pre-sort as I just described or else post-sort within each group. I didn't do this myself because in general it requires an additional parameter to identify the sort criteria – but it should be pretty easy to handle this in a custom way for any particular application.

    Hope this helps!

  18. David Anson says:

    John Zhu,

    I'm afraid I don't understand your question. 🙁 The sample XAML you provide is exactly how I'd implement multiple lines of data – and it works fine in the sample project (once I added a simple Description field to the Animal class). Can you please be more specific about what you think won't work?

  19. John Zhu says:

    Hi Delay,

    Doing the multiple lines of data as above would surely work. But I wasn't sure how you would change the code to disable selection of headers? Surely the code below would have to be modified?

           public object Convert(object value, Type targetType, object parameter, CultureInfo culture)


               var contentControl = value as ContentControl;

               return (null != contentControl) && contentControl.Content is Animal;


  20. John Zhu says:

    my bad, I was able to answer my own question 🙂

  21. David Anson says:

    John Zhu,

    Great! 🙂

  22. John Zhu says:

    Hello Delay,

    Sorry to bother you again, but I have a follow-up question. In using an item template as follows:



                           <StackPanel Margin="0,0,0,15" Width="432">

                               <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Margin="0,0,0,0" Style="{StaticResource PhoneTextNormalStyle}"  FontSize="30"/>

                               <TextBlock Text="{Binding Description}" TextWrapping="Wrap" Margin="0,0,0,0" Style="{StaticResource PhoneTextNormalStyle}"  FontSize="24"/>




    I noticed that the ability to highlight the text foreground in light blue has disappeared, while it was present if a single contentcontrol is used. I was wondering if you know how to enable that again? I know it's doable for a listboxitem like this link (…/styling-wpf-listbox-highlight-color), but I wasn't sure how it would work in the context of a textbox in a stackpanel in a listboxitem.


  23. David Anson says:

    John Zhu,

    The accent-color highlighting of ListBox works by changing the Foreground property of the ListBoxItem that's selected – because Foreground inherits down, it's automatically picked up by the ContentControl by default. However, your template uses PhoneTextNormalStyle which is BasedOn PhoneTextBlockBase which sets the Foreground property itself – thereby overriding the inherited accent color. If you remove the Style setters from your Template above, I think you'll get the accent color back. 🙂

  24. John Zhu says:

    Hello Delay,

    That worked. Thanks for your input.

  25. John Zhu says:

    Hello Delay,

    Just FYI. I have noticed that by using the Setter, my listbox rendering slowed down considerably on an actual device. It's fine on the emulator though. After I commented it out, as below, my performance on the device improve noticeably.

                   <ListBox SelectionChanged="ListBox_SelectionChanged" Name="ListBoxAll"

                   ItemsSource="{Binding ListByDatesAll, Converter={StaticResource GroupingItemsControlConverter},

                       ConverterParameter={StaticResource FancyGroupingItemsControlConverterParameter}}">                    




  26. John Zhu says:

    Hello Delay,

    I have another question about your code. In my original collectoin, I was using an ObserveableCollection<> to keep my items. Things added/removed from this list shows up in the collection properly through data-binding, but I have noticed that gets broken when the IValueConverter is used, possibly because it doesn't return a ObserveableCollection back. I am currently using NotifyPropertyChanged manually, but I think that re-adds the entire collection whenever a single item is added/removed. Therefore, I would like to determine how we can put CollectionChanged back. Do you have any idea?

  27. David Anson says:

    John Zhu,

    The problem with ObservableCollection in this scenario (as you suggest) is that CollectionChanged events don't bubble up and re-run the IValueConverter. Forcing that via INotifyProperty changed is a very reasonable workaround, but quite inefficient as you note. Thinking about this briefly, it seems like the IValueConverter *could* hook up to the INotifyCollectionChanged.CollectionChanged property if it were present and *could* merge any subsequent changes into its list – but that would be quite a bit more complex than the initial "here's how you could mock this up" sample I originally posted. 🙂

    What I'd suggest considering instead is the LongListSelector in the November 2010 release of the Windows Phone Toolkit – it's a control that's designed to support this scenario "properly" (i.e., without an IValueConverter) and performantly. You can read more about it here:…/mo-controls-mo-controls-mo-controls-announcing-the-second-release-of-the-silverlight-for-windows-phone-toolkit.aspx

    Hope this helps!

  28. kamran says:

    Thanks for the great post!

    Just like the above user, ListBox Drag and Drop stopped working  when I added your grouping solution.

    I had implemented drag and drop for ListBox using the same post you mentioned:…/new-with-silverlight-toolkit-drag-and.html

  29. David Anson says:


    I'm sorry to hear that! I'll pass the feedback on to teh author of the drag/drop code to see if he knows why this might be. (Aside: If I had to guess, it'd be that the problem arises because the items in the ListBox doesn't exactly match the items in its ItemsSource (due to the custom IValueConverter)…)

  30. Shane says:

    Does this work with a dynamic source (instead of hard coded collection of objects.). I'm trying to implement this pattern, but in my case I'm using a CollectionViewSource in XAML and binding it to an ObservableCollection of Objects that I have to call out to a Web Service to get. I think whats happening is that all these Converters are firing before I have the data (due to the asynchronous behavior of getting data in Silverlight). Once the data comes back I don't think the Converters are firing again. Any thoughts?

  31. Shane says:

    I forgot to mention that I'm using a MVVM pattern. The VM is my datacontext and has a property with an ObservableCollection of Objects. When I initialize the ViewModel I make a call to get the data and handle the asynchronous response when it comes back.

  32. David Anson says:


    It sounds like you're asking a very similar question to John Zhu – if so, then my reply to him (a page or two up) should apply here as well. 🙂

  33. Shanez says:

    Delay – I'm reviewing the LongListSelector tool with WP7 Toolkit and it looks cool, but should I be using that with a pure Silverlight solution (meaning not a WP7 solution).

  34. David Anson says:


    LongListSelector was specifically developed for (and tested on) Windows Phone 7; being able to use it on Silverlight was not a goal. There are a variety of aspects of LongListSelector that are specifically intended to address Windows Phone 7 limitations which may not be present on Silverlight 4.

    That said, I'd expect it should work prett much as-is (or with only a little bit of tweaking), so this might be an interesting scenario to investigate. Please let us know how it goes! 🙂

  35. Shanez says:

    Ok – I'll give it a shot and see if I can get it working in SL4. My main goal is to be able to add GroupBy functionality to an ItemsControl in SL4 after I get data back from a web service call (couldn't get the Converter example to work with an asynchronous call to get data). Hoping this works! Looks like a good control.

  36. GBelzile says:

    Is it possible to achieve two levels of grouping with this solution?


  37. David Anson says:


    Yes, it should be! Doing so would require some changes to my code (i.e., supporting a collection of collections, perhaps), but the general concept should extend to two levels without issue. 🙂

  38. Veri says:

    Hi David,

    Can I use this in my Windows Phone project? Because I get an error that said "System.Windows.AssemblyPart does not contain a definition for Load"


  39. David Anson says:


    Yes, you can use this idea/code on Windows Phone – and I know of others who have done so. 🙂 The sample is a Silverlight 4 project, but the file you really want to reuse is GroupingItemsControlConverter.cs and it doesn't contain any references to AssemblyPart. It sounds like maybe you tried to reuse a part of the sample that was specific to desktop Silverlight – if you add only GroupingItemsControlConverter.cs to your Windows Phone project, you should be fine.

    Good luck!

  40. Brandon says:

    Will generating the listbox in this way allow for Blendability?  I can't seem to get my test data to show up in my grid and I'm wondering if Blend just can't put the logic together for this one, or if I've missed something else I'd need to statically define.


  41. David Anson says:


    I *think* Blend runs IValueConverters on the design surface, so I'd expect this to work. What I'd recommend is removing the GroupingItemsControlConverter to be sure everything else is hooked up right in order to show the test data at design-time; sometimes that can be a little tricky to get right. Once that's working, drop in the GroupingItemsControlConverter and things should work. If not, then try running the app to verify that the run-time behavior is correct – if that doesn't work, then something's not hooked up right which is probably why it's not working.

    Hope this helps!

  42. Brandon says:

    Thanks David, I'll give it a shot, it works beautifully runtime, but I'd just like to get Blend to show it to tweak my interface.  I'll try out your suggestions right now.

  43. Brandon says:

    Wow, seriously, I completely left out the field I'm grouping on from my test data, sorry for wasting your time!  Thank you very much for your help though!

  44. David Anson says:


    No worries – glad you've got it working! 🙂

  45. Great post. thanks for source code and explanation.

    David, I implemented the GroupConverter and have one issue.

    The point were you set the data context, your ex it was a static array. In my case a async job returns an object and I parse throw to make an array of similar structure. But the groupconverter do not wait for the object and fails to show anything. Can you suggest something in this case.

  46. David Anson says:

    Kishore Joshi,

    Set a breakpoint on the first line of GroupingItemsControlConverter.Convert. If the value parameter has the data you expect, then the problem could be in GroupingItemsControlConverter and you can step through the code there. If it's not called or the value parameter is null (which is what I'm guessing will happen), then the issue is probably that the data change isn't propagating through the Binding to get into the converter in the first place. The most common reason for this is changing a property on a model object and not firing the PropertyChanged event for it. If you're setting DataContext directly, the change should flow through automatically, but if you have a custom class in the middle, I'd look there for a problem like this.

    Good luck!

  47. Thanks for the suggestion. there was a custom class in middle I changed it and not its working good.


    Can you suggest how can I implement a expand collapse feature on the group headers.


  48. David Anson says:

    Kishore Joshi,

    You *might* be able to use the Silverlight Toolkit's Expander control for this, but I kind of doubt it because that would break the ListBox metaphor. The technique I show here is more of a hack than anything else and trying to go further with it could be more trouble than it's worth… 🙂 Maybe consider using a TreeView instead as that supports expand/collapse natively..

  49. John says:

    Hello David,

    Thank you for your post. I've got a different scenario over here.. collectionViewSource has groups after propertydescriptions have been given. I have few null groups also formed. How can I take the items of the null group and tag them with the group higher than that?



  50. David Anson says:


    What I think I would do in your scenario is introduce another property on the view model object which provides a custom property for GroupDescription that returns the same value as whatever you're using now EXCEPT that it maps the problematic null group items into the groups you want them to live in by returning the corresponding value.

Comments are closed.

Skip to main content