Thursday, September 24, 2009

Sorting and filtering databound listview in WPF

This is actually quite a simple thing, but a lot of people don't know it, so I'll explain how it's done. As you know, the concept of databinding in WPF is that you provide the visual elements with their data source and they know what to do with it. For example, give a collection of items to a listview, and the listview will generate its items automatically, without forcing you to get involved with the items' visuals, like back at the days of Windows Forms you had to individually create all ListViewItem elements, and so on. On the other hand, that was more direct approach - you know you have some list view items, and when you want to sort them, you just apply sorting to the collection of visual elements. But in WPF you don't have a direct access to the visual elements (ListViewItem). One way to sort a list view would be to modify the source collection. But it's a bit messy, sometimes the collection is read-only, and sometimes we don't want to change the source data.
So WPF offers a way to deal with this with the so called "View" object. It provides functionality to sort, filter, and group items in listviews or other controls that display a collection of items.

Filtering a ListView

Applying a filter to a listview is pretty simple. First, we need to get the view object from the listview, and then provide a predicate that should tell if an item will be included in the filtered view or not.



// Get the default view from the listview
ICollectionView view = CollectionViewSource.GetDefaultView(lstMovies.ItemsSource);

view.Filter = null;
view.Filter = new Predicate<object>(FilterMovieItem);

And now we define the predicate:



private bool FilterMovieItem(object obj)
{
MovieItem item = obj as MovieItem;
if (item == null) return false;

string textFilter = txtFilter.Text;

if (textFilter.Trim().Length == 0) return true; // the filter is empty - pass all items

// apply the filter
if (item.MovieName.ToLower().Contains(textFilter.ToLower())) return true;
return false;
}

The result is: we now have a working filter for our listview:



Sortinga ListView

The sorting is done in almost the same way. Again we get the view object and then apply some sort descriptions. We don't have to come up with some complicated sorting algorithm, it's enough to provide the property name of the data item by which the listview will be sorted, and the sort direction.



// .....

// Get the default view from the listview
ICollectionView view = CollectionViewSource.GetDefaultView(lstMovies.ItemsSource);

// .....
// .....

view.SortDescriptions.Clear();
view.SortDescriptions.Add(new SortDescription(propertyName, direction));

And now we have a support for sorting the items in the listview:



Of course, sorting and filtering can be applied at the same time, like in this case we have filtered movies which have names containing "matrix" and sorted them by rating:



Download sample application

The sample can be downloaded here: Download sample

3 comments:

  1. This would be such an elegant solution to what I'm trying to do, but I can't manage to make it work. I tried to translate it to VB.NET but it throws an exception while filtering. It states "Specified method is not supported", pointing at this line: view.Filter = New Predicate(Of Object)(AddressOf myFilter)
    Where myFilter is a function, just like yours. Please help.

    ReplyDelete
  2. I have too NotSupportedException =( This method does not work.

    ReplyDelete
  3. this code is working.....
    nice example....

    ReplyDelete