10,000フィートの概要
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);
2つのパラメータと1つの本体LambdaExpression
を持つaを作成します。最初にパラメータを作成しましょう。
var parameters = new[] {
Expression.Parameter(typeof(object), "sender"),
Expression.Parameter(eventArgsType, "e"),
};
ボディはBlockExpression
(これは厳密には必要ではありませんが、後で役立つ可能性があります)になります。ABlockExpression
はいくつかの変数を使用し、他の任意の数の式で構成されます。非常に重要なのは、これらの最後がブロックの戻り値になることです。
上記のモックイベントハンドラーの最初の行は、1つの変数が必要であることを示しています。
var variable = Expression.Variable(sourceType, "source");
そして、戻り値を生成する述語が絶対に必要です。
var predicate = Expression.GreaterThan(
Expression.MakeMemberAccess(variable, sourceType.GetProperty("Number")),
Expression.Constant(10));
これで、の本体を作成する準備が整いましたBlockExpression
。本体はItem
、イベントハンドラー()の2番目のパラメーターのプロパティにアクセスし、型であるため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
、戻り値も生成されます。
これで、ブロック式を作成し、最後にフィルタリングラムダ式自体を作成できます。
var block = Expression.Block(new[] { variable }, body);
var filter = Expression.Lambda<Func<object, FilterEventArgs, bool>>(block, parameters);
フィルターを差し込む
この問題に直面した理由の1つは、コンパイラーが式から「実際の」メソッドを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;
複雑なフィルターの作成
このアプローチを採用するもう1つの理由は、フィルタリングロジックを拡張するのが非常に簡単なことです。たとえば、次のように述語を配線する代わりに、次のように想像するのは簡単です。
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);
}