The 10,000 feet overview
Filtering data in a DataGrid
is done by attaching a handler to the CollectionViewSource.Filter
event, so the actual problem is how to create a FilterEventHandler
from user input. This problem can be solved in a powerful manner with expression trees.
I 'm not going to go into detail with regards to how to parse the input string because that can range from very simple to very complicated; a powerful approach would be to use a tool like ANTRL to parse the input into an abstract syntax tree.
What I will show is how you can create a filter given that the input is parsed and the user's intent is known (it is also quite easy to create the filter on the fly while parsing).
Let's assume that the user entered "Number > 10" for the filter. If this filter were hardcoded, the FilterEventHandler
would look like this:
public void CustomFilterHandler(object sender, FilterEventArgs e)
{
CommDGDataSource source = (CommDGDataSource)e.Item;
return source.Number > 10;
}
Constructing a filtering event handler dynamically
What we 're going to do here is dynamically construct this method on the fly using expression trees. Let's begin with some preamble:
var sourceType = typeof(CommDGDataSource);
var eventArgsType = typeof(System.Windows.Data.FilterEventArgs);
We 're going to be constructing a LambdaExpression
which has two parameters and a body. Let's construct the parameters first:
var parameters = new[] {
Expression.Parameter(typeof(object), "sender"),
Expression.Parameter(eventArgsType, "e"),
};
The body is going to be a BlockExpression
(this is not strictly necessary, but could come in handy later on). A BlockExpression
uses a number of variables and is comprised of any number of other expressions; very importantly, the last of these will be the block's return value.
The first line of the mock event handler given above tells us that we need one variable:
var variable = Expression.Variable(sourceType, "source");
And we definitely need a predicate that produces the return value:
var predicate = Expression.GreaterThan(
Expression.MakeMemberAccess(variable, sourceType.GetProperty("Number")),
Expression.Constant(10));
We are now ready to produce the body of the BlockExpression
. The body will need to access the Item
property of the second parameter of the event handler (parameters[1]
) and cast it to sourceType
because FilterEventArgs.Item
is of type object
, so we can't use it directly. The result of the cast should be stored in variable
and then predicate
will perform the test:
// Some intermediate variables to cut down on the line length
var itemProperty = eventArgsType.GetProperty("Item");
var itemAccessExpression = Expression.MakeMemberAccess(parameters[1], itemProperty);
var castItemToCorrectType = Expression.TypeAs(itemAccessExpression, sourceType);
// And the body is comprised of these two expressions:
var body = new[] { Expression.Assign(variable, castItemToCorrectType), predicate };
Since predicate
is the last expression of the body it will also produce its return value.
We can now construct the block expression and then finally the filtering lambda expression itself:
var block = Expression.Block(new[] { variable }, body);
var filter = Expression.Lambda<Func<object, FilterEventArgs, bool>>(block, parameters);
Plugging in the filter
One of the reasons we went to all this trouble is that now the compiler can construct a "real" method from the filter
expression for us automatically! We can then simply plug it into the CollectionViewSource.Filter
event and enjoy filtering:
// It's a good idea to keep a reference to this around for now,
// so that it can be removed from the event handler later.
var filterMethod = filter.Compile();
collectionViewSource.Filter += filterMethod;
Constructing complicated filters
The other reason to go with this approach is that it's quite easy to extend the filtering logic. For example it's easy to imagine that instead of hardwiring the predicate like this:
var predicate = Expression.GreaterThan(
Expression.MakeMemberAccess(variable, sourceType.GetProperty("Number")),
Expression.Constant(10));
We could construct it from user input like this:
// Maps operators to Expression factory methods
var dict = new Dictionary<string, Func<Expression, Expression, BinaryExpression>>
{
{ "==", Expression.Equal },
{ ">", Expression.GreaterThan },
{ "<", Expression.LessThan },
// etc
};
var predicate = Expression.Constant(true); // by default accept all rows
// Logical AND everything in parseResult to construct the final predicate
foreach (parseResult in parsedUserInput)
{
var exprConstructor = dict[parseResult.Operator];
var property = sourceType.GetProperty(parseResult.PropertyName);
var target = Expression.MakeMemberAccess(variable, property);
var additionalTest = exprConstructor(target, Expression.Constant(parseResult.Value));
predicate = Expression.AndAlso(predicate, additionalTest);
}