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