Слияние двух наблюдаемых коллекций и привязка к списку с помощью Rx - PullRequest
2 голосов
/ 24 августа 2011

Мне нужно объединить 2 ObservableCollection в один и связать его с сеткой, и мне нужны обновления в реальном времени для передачи в сетку.для .eg

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

Таким образом, вышеупомянутая коллекция должна работать в режиме реального времени, когда какой-либо объект добавляется в первую или вторую коллекцию, следует применить фильтрацию и отфильтрованные элементы добавить в коллекцию.Я где-то читал, что Rx Framework действительно может помочь здесь.Пожалуйста, помогите мне заменить код Psudeo выше фактическим кодом Rx.Спасибо.

Ответы [ 2 ]

3 голосов
/ 14 сентября 2011

Вот мое решение для вас:

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 и 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 голосов
/ 24 августа 2011

Я ничего не знаю о Rx Framework, однако ObservableCollections уведомляет пользовательский интерфейс каждый раз, когда изменяется содержимое коллекции, поэтому вам нужно только добавить / удалить элементы из вашей связанной коллекции, чтобы обновление пользовательского интерфейса

Слияние может быть выполнено с помощью сценария, такого как:

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

    return first;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...