WPF: ListCollectionView for sorting, filtering and grouping

I have written before that I need and like WPF lists (ListBox, ListView..) very much. Now I'd like to share a method to manipulate the view when being simply bound to a collection without touching this collection. It can be done in xaml as shown here but I don't like this approach very much because in my opinion operations like sorting, grouping belong to view model not directly view and no tests will be available if they are in view.

Example

So the sample problem is we have a view with a ListBox which ItemsSource is bound to an ObservableCollection. In my example I have a list of True Blood characters. Each creature has first name, last name, age (made up) and kind (it can be either vampire, human or mystery). A model of creature is (very simple just to have some data):

public class CreatureModel
{
public CreatureModel( string firstName, string lastName, int age,
CreatureKind kind )
{
this.FirstName = firstName;
this.LastName = lastName;
this.Age = age;
this.Kind = kind;
}

public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public CreatureKind Kind { get; set; }

public override string ToString()
{
return string.Format( "{0} {1} {2} {3}", this.FirstName,
this.LastName, this.Age, this.Kind );
}
}


View is contains a ListBox and three buttons:

<ListBox ItemsSource="{Binding Path=CreaturesCollection}" />
<Button Content="Sort by age"
Command="{Binding Path=SortByAgeCommand}"/>
<Button Content="Filter Vampires"
Command="{Binding Path=FilterVampiresCommand}"/>
<Button Content="Group by kind"
Command="{Binding Path=GroupByKindCommand}"/>

View model for this problem is also simple – we need a collection of creatures and commands for sorting, filtering and grouping:

public ObservableCollection<CreatureModel> CreaturesCollection 
{ get; private set; }
public ICommand SortByAgeCommand
{ get { return new DelegateCommand(this.SortByAge ); } }
public ICommand FilterVampiresCommand
{ get { return new DelegateCommand( this.FilterVampires ); } }
public ICommand GroupByKindCommand
{ get { return new DelegateCommand(this.GroupByKind ); } }

And now all the magic needed to implement three methods – parameters in DelegateCommand constructor is this method:

private ListCollectionView GetListCollectionView()
{
return (ListCollectionView) CollectionViewSource
.GetDefaultView( this.CreaturesCollection );
}

CollectionViewSource


CollectionViewSource  – when you set a binding to a collection, WPF makes it a binding to the default view for that collection. CollectionViewSource has a static method that lets getting this default view: CollectionViewSource.GetDefaultView and on the view filtering, grouping, sorting can be easily applied ListCollectionView is a default view for collections implementing IList.

Sorting – ListCollectionView.CustomSort

To apply sorting an implementation of IComperer is needed and it can be any implementation of this interface. I will use one that simply sorts creatures by age:

public class SortCreaturesByAge : IComparer
{
public int Compare( object x, object y )
{
if( x as CreatureModel == null && y as CreatureModel == null )
{
throw new ArgumentException( "SortCreatures can
only sort CreatureModel objects."
);
}
if( ((CreatureModel) x).Age > ((CreatureModel) y).Age )
{
return 1;
}
return -1;
}
}


Now I can implement a method used for SortByAgeCommand by setting CustomSort on ListCollectionView:

public ICommand SortByAgeCommand 
{ get { return new DelegateCommand(this.SortByAge ); } }
private void SortByAge()
{
this.GetListCollectionView().CustomSort = new SortCreaturesByAge();
}


Unsorted and sorted collection:


image image

Filtering – ListCollectionView. Filter

I would like to implement a filter which shows only vampires from the list of creatures. Filter is a Predicate<bool>. Even simpler than in sorting, we just need
a method that returns a bool deciding whether a parameter object is what we need:

private bool IsAVampire( object obj )
{
if(obj as CreatureModel != null
&& (obj as CreatureModel).Kind == CreatureKind.Vampire)
{
return true;
}
return false;
}


Clicking on “Filter vampires” shows only vampires :-)

image

Grouping

I would like to group all creatures on my list by kind. Kind is an enum:

public enum CreatureKind
{
Human,
Vampire,
Mystery
}


Again ListCollectionView comes with a solution. All needed to be done is adding a property name that grouping will be based on. I this exaple – kind:

public ICommand GroupByKindCommand 
{ get { return new DelegateCommand(this.GroupByKind ); } }
private void GroupByKind()
{
this.GetListCollectionView().GroupDescriptions.Add(
new PropertyGroupDescription { PropertyName = "Kind" } );
}


Now list can be grouped:

image

Sum up

Grouping, filtering and sorting are supported by WPF very well and are easy to apply. I feel that my example is very basic and like always in WPF lots of customising can be done here. Next thing I plan to discover is a neat and smart way of setting data templates and styles for this kind of problems.

Code here. As always any suggestions very welcome.


Monika

5 Comments

  • Hi.
    I am trying to copy your solution but having problems with GetListCollectionView() which throws an exception "unable to cast ..."
    Difference between your example and mine is mainly that I use BindingList instead of ObsevableCollection and I try dont have CreatureCollection as property like you. My ListBox is in a control templade where its ItemSource is using databinding to its parents Itemsource.
    Any ideas how to get the casting correct.
    //lasse

  • Hi Lasse,
    check in debug what type is returned by CollectionViewSource.GetDefaultView when bound property in not an ObservableCollection.

    Maybe the method you have problems with should return simply ICollectionView. I will check that later.

  • Hi
    If I do like:
    var tmp = CollectionViewSource.GetDefaultView(_listBox.ItemsSource);

    tmp has type BindingListCollectionView. Trying to cast this to ListCollectionView throws a exception.

    I cant find any way to use custom sort on a BindingListCollectionView,, is there?

    //lasse

  • I have no experience with BindingListCollectionView but the same problem is discussed here:
    http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/f3dd977d-589e-41a7-af20-6edf2baeaef0
    And it looks like the answer is unfortunately no.

  • I've seen that and I am not so happy about the "no" answer,,,, anyhow,, thanks for your help.
    //lasse

Comments have been disabled for this content.