10,000 英尺概览
Filtering data in a DataGrid 是通过将处理程序附加到CollectionViewSource.Filter 事件来完成的,因此实际的问题是如何从用户输入中创建FilterEventHandler。这个问题可以通过表达式树以强大的方式解决。
我不打算详细介绍如何解析输入字符串,因为它的范围从非常简单到非常复杂;一种强大的方法是使用 ANTRL 之类的工具将输入解析为抽象语法树。
我将展示如何在输入已解析且用户意图已知的情况下创建过滤器(在解析时动态创建过滤器也很容易)。
假设用户为过滤器输入了“数字 > 10”。如果此过滤器是硬编码的,FilterEventHandler 将如下所示:
public void CustomFilterHandler(object sender, FilterEventArgs e)
{
CommDGDataSource source = (CommDGDataSource)e.Item;
return source.Number > 10;
}
动态构造过滤事件处理程序
我们在这里要做的是使用表达式树动态地构建这个方法。让我们从一些序言开始:
var sourceType = typeof(CommDGDataSource);
var eventArgsType = typeof(System.Windows.Data.FilterEventArgs);
我们将构建一个LambdaExpression,它有两个parameters 和一个body。我们先构造参数:
var parameters = new[] {
Expression.Parameter(typeof(object), "sender"),
Expression.Parameter(eventArgsType, "e"),
};
主体将是BlockExpression(这不是绝对必要的,但以后可能会派上用场)。一个BlockExpression 使用多个variables 并由任意数量的other expressions 组成;非常重要的是,最后一个将是块的返回值。
上面给出的模拟事件处理程序的第一行告诉我们,我们需要一个变量:
var variable = Expression.Variable(sourceType, "source");
我们肯定需要一个产生返回值的谓词:
var predicate = Expression.GreaterThan(
Expression.MakeMemberAccess(variable, sourceType.GetProperty("Number")),
Expression.Constant(10));
我们现在准备生成BlockExpression 的主体。正文将需要访问事件处理程序的第二个参数(parameters[1])的Item 属性并将其转换为sourceType,因为FilterEventArgs.Item 是object 类型,所以我们不能直接使用它.转换结果应存储在variable 中,然后predicate 将执行测试:
// 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 };
由于predicate 是主体的最后一个表达式,它也会产生它的返回值。
我们现在可以构造块表达式,最后是过滤 lambda 表达式本身:
var block = Expression.Block(new[] { variable }, body);
var filter = Expression.Lambda<Func<object, FilterEventArgs, bool>>(block, parameters);
插入过滤器
我们遇到所有这些麻烦的原因之一是,现在编译器可以自动为我们从filter 表达式构造一个“真实”方法!然后我们可以简单地将其插入CollectionViewSource.Filter 事件并享受过滤:
// 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;
构造复杂的过滤器
采用这种方法的另一个原因是它很容易扩展过滤逻辑。例如,很容易想象,而不是像这样硬连线谓词:
var predicate = Expression.GreaterThan(
Expression.MakeMemberAccess(variable, sourceType.GetProperty("Number")),
Expression.Constant(10));
我们可以像这样从用户输入构建它:
// 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);
}