过滤命令设计方法

时间:2012-08-14 06:56:33

标签: c# .net wpf parsing command

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

绑定到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属性已设置为true的数据行

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

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

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

  5. 我已经为我的绑定数据创建了ICollectionView来处理过滤,但我不确定解析命令和正确处理过滤以满足上述要求的方法。

    任何指导都将不胜感激。

3 个答案:

答案 0 :(得分:3)

10,000英尺概述

Filtering data in a DataGrid是通过将处理程序附加到CollectionViewSource.Filter事件来完成的,因此实际问题是如何从用户输入创建FilterEventHandler。这个问题可以通过表达式树以强大的方式解决。

关于如何解析输入字符串,我不打算详细说明,因为它可以从非常简单到非常复杂;一种强大的方法是使用像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,其中包含两个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的正文。正文将需要访问事件处理程序(Item)的第二个参数的parameters[1]属性并将其强制转换为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);
}

答案 1 :(得分:0)

点击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#代码。如果你只有有限数量的非常简单的过滤器,你可以很容易地使用正则表达式。

答案 2 :(得分:0)

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

如果你想在过滤字符串中使用类似SQL(或类似LINQ)的语法,那么你需要用户知道这种语法...特别是,如果你不强烈地持有SQL(LINQ)语法,它将会是痛苦。

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

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

Parsing a string C# LINQ expression