这是我的应用程序的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; }
}
基本上,我正在尝试设计一些用户可以输入某些过滤器命令的东西,并且只显示符合条件的行。以下是一些示例(没有引用),
输入“错误”只应显示其Error属性已设置为true的数据行
输入“source == someipaddress”只应显示匹配的IP地址
输入“number> 100”只应显示数字大于100的行
如果用逗号分隔,则应适用多个条件。 (错误,源== someipaddress)
我已经为我的绑定数据创建了ICollectionView来处理过滤,但我不确定解析命令和正确处理过滤以满足上述要求的方法。
任何指导都将不胜感激。
答案 0 :(得分:3)
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
后,您应该为网格绑定的集合引发通知更改事件,假设为Devices
。 Devices
的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?