Example #1
0
        /// <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);
        }
Example #2
0
        /// <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);
        }