1

这是我的应用程序的 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. 输入“错误”应仅显示错误属性已设置为 true 的数据行

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

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

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

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

任何指导将不胜感激。

4

3 回答 3

3

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);
}
于 2012-08-14T08:32:55.603 回答
0

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

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

于 2012-08-14T07:12:29.250 回答
0

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

如果您想在过滤器字符串中使用类似 SQL(或类似 LINQ)的语法,那么您必须让用户知道该语法......特别是,如果您不会强烈地掌握 SQL(LINQ)语法,那将会很痛苦.

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

但是,如果您决定以这种方式进行过滤,那么您的任务归结为“如何从字符串中获取 LINQ 表达式”。此链接应该可以帮助您:
如何将字符串转换为其等效的 LINQ 表达式树?

解析字符串 C# LINQ 表达式

于 2012-08-14T07:20:31.820 回答