3

プロパティ エディタで、選択したオブジェクトからプロパティ エントリへのマップを実装したいと考えています。たとえば、Visual Studio xaml エディターのように。

ターゲット マップは次のようなものです (または、ReactiveUI の ReactiveCollection を使用しているのでしょうか?)

 Selected objects             Filled categories to display in PropertyEditor  
 -------------------------    ---------------------------------------
 ObservableCollection<obj> -> ObservableCollection<Category>

平易な英語の地図:

  1. オブジェクトからすべての一意のプロパティ タイプを収集する
  2. カテゴリ別にグループ化 (例: テキスト、レイアウト)
  3. 選択したオブジェクトを反映するために、必要に応じてカテゴリを追加/削除します
  4. 必要に応じて、既存のカテゴリからプロパティを追加/削除します

課題は、ブランチを追加/削除せずに宣言的/機能的なコードを持つことです。(私はすでに、非常に醜くエラーが発生しやすい命令/イベントベースのコードを持っています。)

Category コレクションと Property コレクションは通常の演算 (Union、Substract、IsMember) を持つセットであると想定できると思います。

インスピレーションは、単純な 1 対 1 マップに最適な、Paul Betts によるReactiveUIのコードです。

var Models = new ReactiveCollection<ModelClass>();
var ViewModels = Models.CreateDerivedCollection(x => new ViewModelForModelClass(x));
// Now, adding / removing Models means we
// automatically have a corresponding ViewModel
Models.Add(new Model(”Hello!”));
ViewModels.Count();
>>> 1

Seq と F# を使用すると、単純な観測不可能なマップは次のようになります。

selectedObjects
|> Seq.collect GetProperties |> Seq.unique |> Seq.groupBy GetPropertyCategory
|> Seq.map (fun categoryName properies -> CreateCategory(properties))

上記のコードは理論上は問題ありませんが、実際には、選択したオブジェクトが変更されるたびに、すべてのビュー モデルが最初から再作成されます。Rex で達成したいことは、上記のマップのバージョンにインクリメンタル アップデートを適用することです。これにより、WPF は GUI の変更された部分のみを更新します。

4

1 に答える 1

4

これは非常に興味深い問題です:-)。残念ながら、それを簡単に解決できるF#ライブラリはないと思います。私の知る限り、Bindable LINQは、のLINQクエリパターン(つまりSelect、メソッド)SelectManyを実装するための1つの試みでした。これは、基本的に必要なものです。F#から使用するには、LINQ操作をなどの関数でラップできます。WhereObservableCollection<T>map

あなたが言うように、機能はSeq.map上で動作するようでIEnumerableあり、したがってそれらはインクリメンタルではありません。ここで必要なのは、、などの関数ですが、変更が段階的に行われるようmapに実装されています。新しい要素がソースコレクションに追加されると、関数はそれを処理し、結果のコレクションに新しい要素を追加します。 。filtercollectObservableCollection<'T>map

私はそれを少し実験しました、そしてここにそれの実装mapがインクリメンタルです:

open System.Collections.Specialized
open System.Collections.ObjectModel

module ObservableCollection =
  /// Initialize observable collection
  let init n f = ObservableCollection<_>(List.init n f)

  /// Incremental map for observable collections
  let map f (oc:ObservableCollection<'T>) =
    // Create a resulting collection based on current elements
    let res = ObservableCollection<_>(Seq.map f oc)
    // Watch for changes in the source collection
    oc.CollectionChanged.Add(fun change ->
      printfn "%A" change.Action
      match change.Action with
      | NotifyCollectionChangedAction.Add ->
          // Apply 'f' to all new elements and add them to the result
          change.NewItems |> Seq.cast<'T> |> Seq.iteri (fun index item ->
            res.Insert(change.NewStartingIndex + index, f item))
      | NotifyCollectionChangedAction.Move ->
          // Move element in the resulting collection
          res.Move(change.OldStartingIndex, change.NewStartingIndex)
      | NotifyCollectionChangedAction.Remove ->
          // Remove element in the result
          res.RemoveAt(change.OldStartingIndex)
      | NotifyCollectionChangedAction.Replace -> 
          // Replace element with a new one (processed using 'f')
          change.NewItems |> Seq.cast<'T> |> Seq.iteri (fun index item ->
            res.[change.NewStartingIndex + index] <- f item)
      | NotifyCollectionChangedAction.Reset ->
          // Clear everything
          res.Clear()
      | _ -> failwith "Unexpected action!" )
    res

実装mapは非常に簡単でしたが、私はそのように機能し、collectかなりgroupByトリッキーになるのではないかと心配しています。とにかく、これはあなたがそれをどのように使うことができるかを示すサンプルです:

let src = ObservableCollection.init 5 (fun n -> n)
let res = ObservableCollection.map (fun x -> printfn "processing %d" x; x * 10) src

src.Move(0, 1)
src.Remove(0)
src.Clear()
src.Add(5)
src.Insert(0, 3)
src.[0] <- 1

// Compare the original and the result
printfn "%A" (Seq.zip src res |> List.ofSeq)
于 2012-08-22T21:58:59.873 に答える