【问题标题】:Filter Command Design Approach过滤命令设计方法
【发布时间】:2012-08-10 10:40:55
【问题描述】:

这是我的应用程序的 UI。它是一个基于 WPF 的应用程序,可以在设备之间进行分析(与 Wireshark 非常相似)。

正在绑定到 DataGrid 的类如下(除了 Error 和 FuncType 之外的所有内容都绑定到网格:)

public class CommDGDataSource
{
    public int Number { get; set; }
    public string Time { get; set; }
    public string Protocol { get; set; }
    public string Source { get; set; }
    public string Destination { get; set; }
    public string Data { get; set; }
    public bool Error { get; set; }
    public FunctionType FuncType { get; set; }
}

基本上,我正在尝试设计一些东西,用户可以输入某些过滤器命令,并且只应显示与条件匹配的行。以下是一些示例(不带引号),

  1. 输入“Error”应该只显示 Error 属性设置为 true 的数据行

  2. 输入“source==someipaddress”应该只显示匹配的IP地址

  3. 输入“number>100”应该只显示数字大于100的行

  4. 如果用逗号分隔,则应适用多个条件。 (错误,来源==someipaddress)

我已经为我的绑定数据创建了 ICollectionView 来处理过滤,但我不确定我应该采用什么方法来解析命令并正确处理过滤以满足上述要求。

任何指导将不胜感激。

【问题讨论】:

    标签: c# .net wpf parsing command


    【解决方案1】:

    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.Itemobject 类型,所以我们不能直接使用它.转换结果应存储在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);
    }
    

    【讨论】:

    • 特殊答案。非常感谢您的努力。
    • 这对我来说看起来不错,除了最后一部分。 collectionViewSource.Filter += filterMethod; 无法编译,并抱怨它Can't convert Func&lt;object,FilterEventArgs,bool&gt; to Predicate&lt;object&gt;
    • @PeterM 那么您代码左侧的内容似乎不是事件,因为没有事件会接受 Predicate&lt;object&gt; 作为处理程序。
    • 是的,我同意这不是一个事件。我现在看到了我的困惑。 CollectionViewSource.Filter 是一个 event 并根据您的代码构造,但 CollectionView.Filter(我正在使用)是一个 predicate 与我提到的签名。当我寻找表达式树并找到您的代码时,我正在使用应用谓词的 WPF 示例,由于类名的相似性,我将这两个想法混为一谈。 (顺便说一句,有兴趣解决我当前的问题吗?stackoverflow.com/q/44089953/31326
    【解决方案2】:

    就个人而言,我不喜欢您描述的方法。

    如果您想在过滤字符串中使用类似 SQL(或类似 LINQ)的语法,那么您必须让用户知道该语法...成为痛苦。

    我认为,在网格上放置扩展器,其中将包含用于设置过滤的良好旧控件,然后动态构建相应谓词以过滤代码中的数据,会好得多。

    但是,如果您决定以这种方式进行过滤,那么您的任务归结为“如何从字符串中获取 LINQ 表达式”。此链接应该对您有所帮助:
    How to convert a String to its equivalent LINQ Expression Tree?

    Parsing a string C# LINQ expression

    【讨论】:

    • 是的,我认为这也不是一个好主意,但我的客户特别要求采用上述方法,所以不幸的是我坚持使用它:(
    【解决方案3】:

    单击Start 后,您应该为网格绑定到的集合引发通知更改事件,比如说DevicesDevices 的 getter 将应用过滤器并仅返回过滤后的记录。

    void OnStartClicked(object sender, EventArgs e)
    {
       NotifyPropertyChanged("Devices");
    }
    
    public IEnumberable<Device> Devices
    {
       get 
       {
          if(string.IsNullOrEmpty(Filter)) return _devices;
          return _devices.Where(d => d.Satisfies(Filter));
          // or
          return _devices.Where(d => _filter.Satisfies(d, Filter));
       }
    }
    

    Device 类或某些过滤类中,您将定义Satisfies 方法,该方法将按照您的意愿工作。

    对于解析过滤器,您应该使用一些语法 (ANTLR),或者您可以使用 Roslyn 项目将字符串转换为 C# 代码。如果您只有有限数量的非常简单的过滤器,您可以轻松地使用正则表达式。

    【讨论】:

    • 这是个坏主意 - 过滤数据源本身。 WPF 具有优秀的集合视图概念,并且 topicstarter 正朝着正确的方向发展。如果您要过滤数据源,而不是其视图,您将失去用不同视图表示相同数据的可能性。
    猜你喜欢
    • 1970-01-01
    • 2015-08-17
    • 1970-01-01
    • 2011-02-15
    • 1970-01-01
    • 1970-01-01
    • 2019-07-19
    • 2018-03-01
    • 1970-01-01
    相关资源
    最近更新 更多