10,000 英尺概览
过滤 a 中的数据DataGrid
是通过将处理程序附加到CollectionViewSource.Filter
事件来完成的,因此实际问题是如何FilterEventHandler
从用户输入创建 a。这个问题可以用表达式树以一种强大的方式解决。
我不会详细介绍如何解析输入字符串,因为它的范围从非常简单到非常复杂;一种强大的方法是使用像 ANTRL 这样的工具将输入解析为抽象语法树。
我将展示的是如何在输入已解析且用户意图已知的情况下创建过滤器(在解析时动态创建过滤器也很容易)。
假设用户为过滤器输入了“Number > 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
有两个参数和一个主体的。我们先构造参数:
var parameters = new[] {
Expression.Parameter(typeof(object), "sender"),
Expression.Parameter(eventArgsType, "e"),
};
身体将是一个BlockExpression
(这不是绝对必要的,但以后可能会派上用场)。ABlockExpression
使用许多变量,并且由任意数量的其他表达式组成;非常重要的是,最后一个将是块的返回值。
上面给出的模拟事件处理程序的第一行告诉我们,我们需要一个变量:
var variable = Expression.Variable(sourceType, "source");
我们肯定需要一个产生返回值的谓词:
var predicate = Expression.GreaterThan(
Expression.MakeMemberAccess(variable, sourceType.GetProperty("Number")),
Expression.Constant(10));
我们现在准备生产BlockExpression
. 主体将需要访问Item
事件处理程序的第二个参数 ( parameters[1]
) 的属性并将其强制转换为,sourceType
因为FilterEventArgs.Item
是 type 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);
}