【问题标题】:How to implement a non-trivial map between collections in Reactive Extensions?如何在 Reactive Extensions 中的集合之间实现一个重要的映射?
【发布时间】:2012-08-22 16:23:41
【问题描述】:

我想在属性编辑器中实现一个从选定对象属性条目的映射。例如在 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。

灵感来自 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 的更改部分。

【问题讨论】:

    标签: c# wpf user-interface f# system.reactive


    【解决方案1】:

    这是一个非常有趣的问题:-)。遗憾的是,我认为没有任何 F# 库可以让您轻松解决这个问题。据我所知,Bindable LINQ 是为ObservableCollection&lt;T&gt; 实现 LINQ 查询模式(即SelectSelectManyWhere 方法)的一种尝试,这基本上是您所需要的。要从 F# 中使用它,您可以将 LINQ 操作包装在 map 等函数中。

    正如您所说,Seq.map 之类的函数在 IEnumerable 上工作,因此它们不是增量的。你需要的是像mapfiltercollect这样的函数,但是在ObservableCollection&lt;'T&gt;上实现的方式是它们增量地进行更改——当新元素添加到源集合中时,@ 987654333@ 函数将处理它并将新元素添加到结果集合中。

    我对此进行了一些实验,这是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 很容易,但恐怕像collectgroupBy 这样的函数会很棘手。无论如何,这里有一个示例,展示了如何使用它:

    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)
    

    【讨论】:

    • 谢谢托马斯!现在我正在一个小型图书馆中开发它。例如: map : ('T -> 'A) -> RxSeq -> RxSeq 其中 type RxSeq = IObservableWithState, ObservableCollection>
    • @Alfa07 不错!你打算在某个地方共享图书馆吗?如果是这样,也许我可以贡献更多的功能。
    • 是的,当然!那会很好!一旦我恢复正常,我会分享它并将链接发送给你。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-22
    • 2015-04-14
    相关资源
    最近更新 更多