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:
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 :-)
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:
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