合并两个可观察的集合并使用Rx绑定到列表框

问题描述:

我需要将2个ObservableCollection合并为一个并将其绑定到网格,并需要实时更新才能流入网格。例如,合并两个可观察的集合并使用Rx绑定到列表框

ObservableCollection<int> First = new ObservableCollection<int>(); 
ObservableCollection<int> Second = new ObservableCollection<int>(); 

//Some Rx Psuedo Code (p.s. this is not the actual code, this is where i need help) 
{ 
    var guicollection = First 
     .Where(i => i%2) 
     .Merge(Second.Where(i => i % 3)).ToCollection(); 
} 

listBox1.ItemsSource = guidcollection; 

First.Add(1); 
First.Add(2); 
First.Add(3); 
First.Add(4); 
First.Add(5); 
Second.Add(1); 
Second.Add(2); 
Second.Add(3); 
Second.Add(4); 

// Now the guicollection should have the following items 2,4 from FirstCollection 
// and 3 from second collection 

所以上面guicollection应该工作实时wheneve一个对象被添加到第一或第二集合中的滤波应该应用和经过滤项应该被添加到guicollection。我在某处读到Rx框架在这里真的可以帮到你。请帮助我用实际的Rx代码替换上面的Psudeo代码。谢谢。

+0

如果你需要多个'ItemsSource',你可以使用'CompositeCollection' –

+0

谢谢。 CompositeCollection是否也支持Where和实时更新。 –

+0

是的,它确实没有 –

我不知道在Rx框架什么,但是ObservableCollections通知UI随时随地集合的内容更改,从绑定的集合,所以你应该只需要添加/删除项目有UI更新

的撤并可以通过脚本来实现,如以下几点:

public ObservableCollection<object> MergeCollections(
    ObservableCollection<object> first, 
    ObservableCollection<object> second) 
{ 
    foreach(var item in second) 
    { 
     if (!(first.Contains(item))) 
      first.Add(item); 
    } 

    return first; 
} 
+0

谢谢。但我正在从Rx框架的角度来看待某些东西。 –

这里是我的解决方案为您提供:

Func<ObservableCollection<int>, 
    Func<int, bool>, 
    IObservable<int>> getAddsWhere = 
     (oc, pred) => 
      from ep in Observable 
       .FromEventPattern<NotifyCollectionChangedEventHandler, 
        NotifyCollectionChangedEventArgs>(
         h => oc.CollectionChanged += h, 
         h => oc.CollectionChanged -= h) 
      where ep.EventArgs.Action == NotifyCollectionChangedAction.Add 
      from i in ep.EventArgs.NewItems.OfType<int>() 
      where pred(i) 
      select i; 

var firsts = getAddsWhere(First, i => i % 2 == 0); 
var seconds = getAddsWhere(Second, i => i % 3 == 0); 

var boths = firsts.Merge(seconds); 

boths.Subscribe(i => guicollection.Add(i)); 

我测试并按照您的要求工作 - 2,4结束于guicollection


编辑:更改为显示如何处理所有的NotifyCollectionChangedAction枚举值。

NotifyCollectionChangedAction枚举有五个值:

  1. Add
  2. Move
  3. Remove
  4. Replace
  5. Reset

Move无关 - 这只是一个内部操作。

NewItems集合上NotifyCollectionChangedEventArgs包含Add & Replace值。

OldItems集合上NotifyCollectionChangedEventArgs包含Remove & Replace值。

棘手的操作是Reset - 在集合上调用Clear()时发生 - 因为它不会告诉您哪些项目已清除,并且在事件引发时已清除项目。

因此,唯一的解决方案是创建一个返回IObservable<ObservableCollectionOperation<T>>,并在内部跟踪的变化,使一系列消除了可以在Clear被称为发出的扩展方法。

在我倾倒大量代码之前,我会告诉你调用代码是什么样的。这非常简单直接。

var FirstOps = First.ToOperations(i => i % 2 == 0); 
var SecondOps = Second.ToOperations(i => i % 3 == 0); 

var BothOps = FirstOps.Merge(SecondOps); 

var subscription = BothOps.Subscribe(guicollection); 

非常整齐,是吧?

ObservableCollectionOperation<T>定义像这样:

public class ObservableCollectionOperation<T> 
{ 
    public readonly T Value; 
    public readonly Operation Operation; 

    public static ObservableCollectionOperation<T> Add(T value) 
    { 
     return new ObservableCollectionOperation<T>(value, Operation.Add); 
    } 

    public static ObservableCollectionOperation<T> Remove(T value) 
    { 
     return new ObservableCollectionOperation<T>(value, Operation.Remove); 
    } 

    public ObservableCollectionOperation(T value, Operation operation) 
    { 
     this.Value = value; 
     this.Operation = operation; 
    } 

    public override int GetHashCode() 
    { 
     return this.Value.GetHashCode() 
      * (this.Operation == Operation.Add ? 1 : -1); 
    } 

    public override bool Equals(object obj) 
    { 
     if (obj is ObservableCollectionOperation<T>) 
     { 
      var other = obj as ObservableCollectionOperation<T>; 
      return this.Value.Equals(other.Value) 
        && this.Operation.Equals(other.Operation); 
     } 
     return false; 
    } 
} 

Operation枚举需要添加和删除项之间进行区分,并且它不出所料看起来像这样:

public enum Operation 
{ 
    Add, 
    Remove, 
} 

现在的扩展方法。

public static IObservable<ObservableCollectionOperation<T>> 
    ToOperations<T>(this ObservableCollection<T> @this) 
{ 
    return Observable.Create<ObservableCollectionOperation<T>>(o => 
    { 
     var local = new List<T>(@this); 

     Func<NotifyCollectionChangedEventArgs, 
      ObservableCollectionOperation<T>[]> 
       getAdds = ea => 
       { 
        var xs = new T[] { }; 
        if (
         ea.Action == NotifyCollectionChangedAction.Add 
         || ea.Action == NotifyCollectionChangedAction.Replace) 
        { 
         xs = ea.NewItems.Cast<T>().ToArray(); 
         local.AddRange(xs); 
        } 
        return xs 
         .Select(x => 
          ObservableCollectionOperation<T>.Add(x)) 
         .ToArray(); 
       }; 

     Func<NotifyCollectionChangedEventArgs, 
      ObservableCollectionOperation<T>[]> 
       getRemoves = ea => 
       { 
        var xs = new T[] { }; 
        if (
         ea.Action == NotifyCollectionChangedAction.Remove 
         || ea.Action == NotifyCollectionChangedAction.Replace) 
        { 
         xs = ea.OldItems.Cast<T>().ToArray(); 
         Array.ForEach(xs, x => local.Remove(x)); 
        } 
        return xs 
         .Select(x => 
          ObservableCollectionOperation<T>.Remove(x)) 
         .ToArray(); 
       }; 

     Func<NotifyCollectionChangedEventArgs, 
      ObservableCollectionOperation<T>[]> 
       getClears = ea => 
       { 
        var xs = new T[] { }; 
        if (ea.Action == NotifyCollectionChangedAction.Reset) 
        { 
         xs = local.ToArray(); 
         local.Clear(); 
        } 
        return xs 
         .Select(x => 
          ObservableCollectionOperation<T>.Remove(x)) 
         .ToArray(); 
       }; 

     var changes = 
      from ep in Observable 
       .FromEventPattern<NotifyCollectionChangedEventHandler, 
        NotifyCollectionChangedEventArgs>(
         h => @this.CollectionChanged += h, 
         h => @this.CollectionChanged -= h) 
      let adds = getAdds(ep.EventArgs) 
      let removes = getRemoves(ep.EventArgs) 
      let clears = getClears(ep.EventArgs) 
      from x in clears.Concat(removes).Concat(adds).ToObservable() 
      select x; 

     return changes.Subscribe(o); 
    }); 
} 

我增加了一个重载的扩展方法,以帮助过滤:

public static IObservable<ObservableCollectionOperation<T>> 
    ToOperations<T>(
     this ObservableCollection<T> @this, 
     Func<T, bool> filter) 
{ 
    return @this.ToOperations().Where(op => filter(op.Value)); 
} 

最后我创造一个辅助方法,以使观察到的操作被打成了一个“观察者” ObservableCollection<T>

public static IDisposable 
    Subscribe<T>(
     this IObservable<ObservableCollectionOperation<T>> @this, 
     ObservableCollection<T> observer) 
{ 
    return @this.Subscribe(op => 
    { 
     switch (op.Operation) 
     { 
      case Operation.Add : 
       observer.Add(op.Value); 
       break; 
      case Operation.Remove : 
       observer.Remove(op.Value); 
       break; 
     } 
    }); 
} 

现在,是的,这是处理删除,它适用于您提供的示例操作。 :-)

+0

这也会照顾到删除。 –

+0

@ like.no.other - 不,它不会。删除更困难。事实上,当你调用'ObservableCollection .Clear()''CollectionChanged'事件不会让你知道清除的值,所以你需要保留你的订阅集合的副本,调用。当你必须这样做时,最好编写你自己的扩展方法。我会编辑我的答案来向你展示。 – Enigmativity

+0

@Enigmativity这是一个很好的方法。谢谢。 –