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. 「Error」と入力すると、Errorプロパティがtrueに設定されているデータ行のみが表示されます。

  2. 「source==someipaddress」と入力すると、一致するIPアドレスのみが表示されます。

  3. 「number>100」と入力すると、100より大きい数値の行のみが表示されます。

  4. カンマで区切る場合は、複数の条件を適用する必要があります。(エラー、ソース== someipaddress)

フィルタリングを処理するためのバインディングデータ用のICollectionViewを既に作成しましたが、コマンドを解析し、上記の要件を満たすためにフィルタリングを適切に処理するために取るべきアプローチがわかりません。

任意のガイダンスをいただければ幸いです。

4

3 に答える 3

3

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]にキャストする必要があるため、直接使用することはできません。キャストの結果はに保存する必要があり、テストを実行します。sourceTypeFilterEventArgs.Itemobjectvariablepredicate

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

クリックした後Start、グリッドがバインドされているコレクションの変更通知イベントを発生させる必要がありますDevices。のゲッターDevicesはフィルターを適用し、フィルターされたレコードのみを返します。

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 に答える