Exemplo n.º 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);

            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);
        }
Exemplo n.º 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="onError">The handler for errors from the Observable. If
        /// not specified, an error will go to DefaultExceptionHandler.</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,
            Action <Exception> onError = null)
        {
            var ret = new ReactiveCollection <T>();

            onError = onError ?? (ex => RxApp.DefaultExceptionHandler.OnNext(ex));
            if (withDelay == null)
            {
                fromObservable.ObserveOn(RxApp.DeferredScheduler).Subscribe(ret.Add, onError);
                return(ret);
            }

            // On a timer, dequeue items from queue if they are available
            var queue      = new Queue <T>();
            var disconnect = Observable.Timer(withDelay.Value, 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, onError);

            // 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);
        }
Exemplo n.º 3
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.
        ///
        /// Note that even though this method attaches itself to any
        /// IEnumerable, it will only detect changes from objects implementing
        /// INotifyCollectionChanged (like ReactiveCollection). If your source
        /// collection doesn't implement this, signalReset is the way to signal
        /// the derived collection to reorder/refilter itself.
        /// </summary>
        /// <param name="selector">A Select function that will be run on each
        /// item.</param>
        /// <param name="filter">A filter to determine whether to exclude items
        /// in the derived collection.</param>
        /// <param name="orderer">A comparator method to determine the ordering of
        /// the resulting collection.</param>
        /// <param name="signalReset">When this Observable is signalled,
        /// the derived collection will be manually
        /// reordered/refiltered.</param>
        /// <returns>A new collection whose items are equivalent to
        /// Collection.Select().Where().OrderBy() and will mirror changes
        /// in the initial collection.</returns>
        public static ReactiveCollection <TNew> CreateDerivedCollection <T, TNew, TDontCare>(
            this IEnumerable <T> This,
            Func <T, TNew> selector,
            Func <T, bool> filter               = null,
            Func <TNew, TNew, int> orderer      = null,
            IObservable <TDontCare> signalReset = null)
        {
            Contract.Requires(selector != null);

            var thisAsColl  = (IList <T>)This;
            var collChanged = new Subject <NotifyCollectionChangedEventArgs>();

            if (selector == null)
            {
                selector = (x => (TNew)Convert.ChangeType(x, typeof(TNew), CultureInfo.CurrentCulture));
            }

            var origEnum = (IEnumerable <T>)thisAsColl;

            origEnum = (filter != null ? origEnum.Where(filter) : origEnum);
            var enumerable = origEnum.Select(selector);

            enumerable = (orderer != null ? enumerable.OrderBy(x => x, new FuncComparator <TNew>(orderer)) : enumerable);

            var ret = new ReactiveCollection <TNew>(enumerable);

            var incc = This as INotifyCollectionChanged;

            if (incc != null)
            {
                ((INotifyCollectionChanged)This).CollectionChanged += (o, e) => collChanged.OnNext(e);
            }

            if (filter != null && orderer == null)
            {
                throw new Exception("If you specify a filter, you must also specify an ordering function");
            }

            signalReset.Subscribe(_ => collChanged.OnNext(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)));

            collChanged.Subscribe(args => {
                if (args.Action == NotifyCollectionChangedAction.Reset)
                {
                    using (ret.SuppressChangeNotifications()) {
                        ret.Clear();
                        enumerable.ForEach(ret.Add);
                    }

                    ret.Reset();
                    return;
                }

                int oldIndex = (args.Action == NotifyCollectionChangedAction.Replace ?
                                args.NewStartingIndex : args.OldStartingIndex);

                if (args.OldItems != null)
                {
                    // NB: Tracking removes gets hard, because unless the items
                    // are objects, we have trouble telling them apart. This code
                    // is also tart, but it works.
                    foreach (T x in args.OldItems)
                    {
                        if (filter != null && !filter(x))
                        {
                            continue;
                        }
                        if (orderer == null)
                        {
                            ret.RemoveAt(oldIndex);
                            continue;
                        }
                        for (int i = 0; i < ret.Count; i++)
                        {
                            if (orderer(ret[i], selector(x)) == 0)
                            {
                                ret.RemoveAt(i);
                            }
                        }
                    }
                }

                if (args.NewItems != null)
                {
                    foreach (T x in args.NewItems)
                    {
                        if (filter != null && !filter(x))
                        {
                            continue;
                        }
                        if (orderer == null)
                        {
                            ret.Insert(args.NewStartingIndex, selector(x));
                            continue;
                        }

                        var toAdd = selector(x);
                        ret.Insert(positionForNewItem(ret, toAdd, orderer), toAdd);
                    }
                }
            });

            return(ret);
        }