Skip to main content Skip to footer

CollectionView Filtering Made Easy

The ICollectionView interface is the primary data view object in WPF. It's essentially a view of the underlying data source that allows you to manipulate your data without actually modifying the underlying values. You define your own rules for sorting, filtering and grouping, etc.

ICollectionView has a 'Filter' member that allows you to specify a predicate to be used for filtering the collection members. To create a filter, you define a method that provides your filtering logic and returns true for each item to include in the view. This is a flexible approach, however, in many cases you may want to serialize the filter so it can be saved and re-applied later. This is a common approach with the System.Data classes because the DataView class has a RowFilter property that takes a string. For example: dv.RowFilter = "Name LIKE 'a*'" would return all items starting with the letter 'A.'

Now, let's demonstrate how we can leverage the expression parser in the System.Data classes to use string expressions as filters in ICollectionView classes. I'll even show how we can do this in Silverlight, where the System.Data classes do not exist!

ViewFilter Class

The ViewFilter class works by creating a DataTable column for each property of the object in the source collection view, plus one additional calculated column with the filter expression. To apply the filter, the class populates the row with data from the item, then returns the value of the calculated expression. Here is the ViewFilter class in its entirety:

using System;  
using System.Data;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  

namespace CollectionViewFilter  
{  
    public class ViewFilter  
    {  
        // ** fields  
        System.ComponentModel.ICollectionView _view;  
        string _filterExpression;  
        DataTable _dt;  

        // ** ctor  
        public ViewFilter(System.ComponentModel.ICollectionView view)  
        {  
            _view = view;  
        }  

        // ** object model  
        public string FilterExpression  
        {  
            get { return _filterExpression; }  
            set  
            {  
                _filterExpression = value;  
                UpdateFilter();  
                _view.Filter = null;  
                if (!string.IsNullOrEmpty(_filterExpression))  
                {  
                    _view.Filter = FilterPredicate;  
                }  
            }  
        }  

        // ** implementation  
        bool FilterPredicate(object obj)  
        {  
            // populate the row  
            var row = _dt.Rows[0];  
            foreach (var pi in obj.GetType().GetProperties())  
            {  
                row[pi.Name] = pi.GetValue(obj, null);  
            }  

            // compute the expression  
            return (bool)row["_filter"];  
        }  
        void UpdateFilter()  
        {  
            _dt = null;  
            if (\_view.CurrentItem != null && !string.IsNullOrEmpty(\_filterExpression))  
            {  
                // build/rebuild data table  
                var dt = new DataTable();  
                foreach (var pi in _view.CurrentItem.GetType().GetProperties())  
                {  
                    dt.Columns.Add(pi.Name, pi.PropertyType);  
                }  

                // add calculated column  
                dt.Columns.Add("\_filter", typeof(bool), \_filterExpression);  

                // create a single row for evaluating expressions  
                if (dt.Rows.Count == 0)  
                {  
                    dt.Rows.Add(dt.NewRow());  
                }  

                // done, save table  
                _dt = dt;  
            }  
        }  
    }  
}

You can use this on any WPF list control (ListBox, ComboBox, DataGrid, C1FlexGrid, etc). Filtering is now made easy because we simply provide a string defining our filter. Here's how you would use the ViewFilter class.

// create a regular ICollectionView  
var view = new ListCollectionView(myList);  

// create the ViewFilter class to control the view filter  
var filter = new ViewFilter(view);  

// apply filter expression at any time  
filter.FilterExpression = "State = 'PENDING' OR State = 'FAILURE'";  

// bind view to controls  
_listcontrol.ItemsSource = view;

We could, if we wanted, set up a TextBox and a Button on our page allowing the user to write their own filter expression. Apply the filter by simply setting the FilterExpression property.

// apply filter on button click or enter key  
void Button_Click(object sender, RoutedEventArgs e)  
{  
    filter.FilterExpression = _txtFilter.Text;  
}

Hunter Haaf