/// <summary> /// Creates a collection whose contents will "follow" another /// collection; this method is useful for creating ViewModel collections /// that are automatically updated when the respective Model collection /// is updated. /// </summary> /// <param name="selector">A Select function that will be run on each /// item.</param> /// <returns>A new collection whose items are equivalent to /// Collection.Select(selector) and will mirror the initial collection.</returns> public static ReactiveCollection <TNew> CreateDerivedCollection <T, TNew>( this ObservableCollection <T> This, Func <T, TNew> selector) { Contract.Requires(selector != null); #if !IOS // Contract.Result is borked in Mono Contract.Ensures(Contract.Result <ReactiveCollection <TNew> >().Count == This.Count); #endif var ret = new ReactiveCollection <TNew>(This.Select(selector)); var coll_changed = new Subject <NotifyCollectionChangedEventArgs>(); This.CollectionChanged += (o, e) => coll_changed.OnNext(e); /* XXX: Ditto as from above * var coll_changed = Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>( * x => This.CollectionChanged += x, x => This.CollectionChanged -= x); */ coll_changed.Subscribe(x => { switch (x.Action) { case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Replace: // NB: SL4 fills in OldStartingIndex with -1 on Replace :-/ int old_index = (x.Action == NotifyCollectionChangedAction.Replace ? x.NewStartingIndex : x.OldStartingIndex); if (x.OldItems != null) { foreach (object _ in x.OldItems) { ret.RemoveAt(old_index); } } if (x.NewItems != null) { foreach (T item in x.NewItems.Cast <T>()) { ret.Insert(x.NewStartingIndex, selector(item)); } } break; case NotifyCollectionChangedAction.Reset: ret.Clear(); break; default: break; } }); return(ret); }
/// <summary> /// Creates a collection based on an an Observable by adding items /// provided until the Observable completes, optionally ensuring a /// delay. Note that if the Observable never completes and withDelay is /// set, this method will leak a Timer. This method also guarantees that /// items are always added via the UI thread. /// </summary> /// <param name="fromObservable">The Observable whose items will be put /// into the new collection.</param> /// <param name="withDelay">If set, items will be populated in the /// collection no faster than the delay provided.</param> /// <returns>A new collection which will be populated with the /// Observable.</returns> public static ReactiveCollection <T> CreateCollection <T>( this IObservable <T> fromObservable, TimeSpan?withDelay = null) { var ret = new ReactiveCollection <T>(); if (withDelay == null) { fromObservable.ObserveOn(RxApp.DeferredScheduler).Subscribe(ret.Add); return(ret); } // On a timer, dequeue items from queue if they are available var queue = new Queue <T>(); var disconnect = Observable.Timer(withDelay.Value, RxApp.DeferredScheduler) .Subscribe(_ => { if (queue.Count > 0) { ret.Add(queue.Dequeue()); } }); // When new items come in from the observable, stuff them in the queue. // Using the DeferredScheduler guarantees we'll always access the queue // from the same thread. fromObservable.ObserveOn(RxApp.DeferredScheduler).Subscribe(queue.Enqueue); // This is a bit clever - keep a running count of the items actually // added and compare them to the final count of items provided by the // Observable. Combine the two values, and when they're equal, // disconnect the timer ret.ItemsAdded.Scan(0, ((acc, _) => acc + 1)).Zip(fromObservable.Aggregate(0, (acc, _) => acc + 1), (l, r) => (l == r)).Where(x => x).Subscribe(_ => disconnect.Dispose()); return(ret); }