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;  
}

Filtering ICollectionView in Silverlight

The approach above for filtering in WPF will not work in Silverlight because we don't have access to the System.Data classes (ie DataTable). This is one scenario where ComponentOne Data for Silverlight can be very useful. C1Data for Silverlight is an implementation of the standard DataSet, DataTable, and DataView classes. The C1.Silverlight.Data assembly supports all Silverlight bindable controls and even LINQ.

To use C1.Silverlight.Data (part of Studio for Silverlight) simply add a reference to the C1.Silverlight.Data.dll assembly, and then add the following namespace to the ViewFilter class file.

using C1.Silverlight.Data;

Also note that Silverlight uses a different implementation of the ICollectionView interface, PagedCollectionView rather than ListCollectionView. Aside from that, the rest of the code remains identical to the WPF version thanks to C1Data.

You can download a sample of the ViewFilter class using C1Data here.

Greg Lutz

comments powered by Disqus