使用Rx将两个可观察的集合和绑定合并到Listbox

时间:2011-08-24 15:46:46

标签: wpf system.reactive reactiveui

我需要将2个ObservableCollection合并为一个并将其绑定到网格,并需要实时更新才能流向网格。 .e.g。

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应该实时工作,当一个对象被添加到第一个或第二个集合时,应该应用过滤,并且过滤的项目应该被添加到guicollection中。我在某处读到Rx框架在这里真的有帮助。请帮我用实际的Rx代码替换上面的Psudeo代码。感谢。

2 个答案:

答案 0 :(得分:3)

以下是我的解决方案:

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,3和&amp; 4结束于guicollection


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

NotifyCollectionChangedAction枚举有五个值:

  1. Add
  2. Move
  3. Remove
  4. Replace
  5. Reset
  6. Move无关 - 它只是一个内部操作。

    NewItems上的NotifyCollectionChangedEventArgs集合包含Add&amp;的值{} Replace

    OldItems上的NotifyCollectionChangedEventArgs集合包含Remove&amp;的值{} 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;
            }
        });
    }
    

    现在,是的,这会处理删除,它可以处理您提供的示例操作。 : - )

答案 1 :(得分: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;
}