/// <summary>
        /// Constructs an ObservableAsPropertyHelper object.
        /// </summary>
        /// <param name="observable">The Observable to base the property on.</param>
        /// <param name="onChanged">The action to take when the property
        /// changes, typically this will call the ViewModel's
        /// RaisePropertyChanged method.</param>
        /// <param name="onChanging">The action to take when the property
        /// changes, typically this will call the ViewModel's
        /// RaisePropertyChanging method.</param>
        /// <param name="initialValue">The initial value of the property.</param>
        /// <param name="deferSubscription">
        /// A value indicating whether the <see cref="ObservableAsPropertyHelper{T}"/>
        /// should defer the subscription to the <paramref name="observable"/> source
        /// until the first call to <see cref="Value"/>, or if it should immediately
        /// subscribe to the the <paramref name="observable"/> source.
        /// </param>
        /// <param name="scheduler">The scheduler that the notifications will be
        /// provided on - this should normally be a Dispatcher-based scheduler
        /// </param>
        public ObservableAsPropertyHelper(
            IObservable <T> observable,
            Action <T> onChanged,
            Action <T> onChanging  = null,
            T initialValue         = default(T),
            bool deferSubscription = false,
            IScheduler scheduler   = null)
        {
            Contract.Requires(observable != null);
            Contract.Requires(onChanged != null);

            scheduler  = scheduler ?? CurrentThreadScheduler.Instance;
            onChanging = onChanging ?? (_ => { });

            var subj      = new ScheduledSubject <T>(scheduler);
            var exSubject = new ScheduledSubject <Exception>(CurrentThreadScheduler.Instance, RxApp.DefaultExceptionHandler);

            subj.Subscribe(x => {
                onChanging(x);
                _lastValue = x;
                onChanged(x);
            }, exSubject.OnNext);

            ThrownExceptions = exSubject;

            _lastValue = initialValue;
            _source    = observable.StartWith(initialValue).DistinctUntilChanged().Multicast(subj);
            if (!deferSubscription)
            {
                _inner     = _source.Connect();
                _activated = 1;
            }
        }
        /// <summary>
        /// Constructs an ObservableAsPropertyHelper object.
        /// </summary>
        /// <param name="observable">The Observable to base the property on.</param>
        /// <param name="onChanged">The action to take when the property
        /// changes, typically this will call the ViewModel's
        /// RaisePropertyChanged method.</param>
        /// <param name="initialValue">The initial value of the property.</param>
        /// <param name="scheduler">The scheduler that the notifications will be
        /// provided on - this should normally be a Dispatcher-based scheduler
        /// (and is by default)</param>
        public ObservableAsPropertyHelper(
            IObservable <T> observable,
            Action <T> onChanged,
            T initialValue       = default(T),
            IScheduler scheduler = null)
        {
            Contract.Requires(observable != null);
            Contract.Requires(onChanged != null);

            scheduler  = scheduler ?? RxApp.DeferredScheduler;
            _lastValue = initialValue;

            var subj      = new ScheduledSubject <T>(scheduler);
            var exSubject = new ScheduledSubject <Exception>(scheduler, RxApp.DefaultExceptionHandler);

            subj.Subscribe(x => {
                this.Log().Debug("Property helper {0:X} changed", this.GetHashCode());
                _lastValue = x;
                onChanged(x);
            }, exSubject.OnNext);

            ThrownExceptions = exSubject;

            // Fire off an initial RaisePropertyChanged to make sure bindings
            // have a value
            subj.OnNext(initialValue);

            var src = observable.DistinctUntilChanged().Multicast(subj);

            _inner  = src.Connect();
            _source = src;
        }
        /// <summary>
        /// Constructs an ObservableAsPropertyHelper object.
        /// </summary>
        /// <param name="observable">The Observable to base the property on.</param>
        /// <param name="onChanged">The action to take when the property
        /// changes, typically this will call the ViewModel's
        /// RaisePropertyChanged method.</param>
        /// <param name="onChanging">The action to take when the property
        /// changes, typically this will call the ViewModel's
        /// RaisePropertyChanging method.</param>
        /// <param name="initialValue">The initial value of the property.</param>
        /// <param name="scheduler">The scheduler that the notifications will be
        /// provided on - this should normally be a Dispatcher-based scheduler
        /// (and is by default)</param>
        public ObservableAsPropertyHelper(
            IObservable <T> observable,
            Action <T> onChanged,
            Action <T> onChanging = null,
            T initialValue        = default(T),
            IScheduler scheduler  = null)
        {
            Contract.Requires(observable != null);
            Contract.Requires(onChanged != null);

            scheduler  = scheduler ?? CurrentThreadScheduler.Instance;
            onChanging = onChanging ?? (_ => {});
            _lastValue = initialValue;

            var subj      = new ScheduledSubject <T>(scheduler);
            var exSubject = new ScheduledSubject <Exception>(CurrentThreadScheduler.Instance, RxApp.DefaultExceptionHandler);

            bool firedInitial = false;

            subj.Subscribe(x => {
                // Suppress a non-change between initialValue and the first value
                // from a Subscribe
                if (firedInitial && EqualityComparer <T> .Default.Equals(x, _lastValue))
                {
                    return;
                }

                onChanging(x);
                _lastValue = x;
                onChanged(x);
                firedInitial = true;
            }, exSubject.OnNext);

            ThrownExceptions = exSubject;

            // Fire off an initial RaisePropertyChanged to make sure bindings
            // have a value
            subj.OnNext(initialValue);
            _source = observable.DistinctUntilChanged().Multicast(subj);

            if (ModeDetector.InUnitTestRunner())
            {
                _inner = _source.Connect();
            }
        }
예제 #4
0
        ISubject <T> setupSubjectIfNecessary <T>(string contract)
        {
            ISubject <T> ret = null;

            withMessageBus(typeof(T), contract, (mb, tuple) => {
                NotAWeakReference subjRef;
                if (mb.TryGetValue(tuple, out subjRef) && subjRef.IsAlive)
                {
                    ret = (ISubject <T>)subjRef.Target;
                    return;
                }

                ret       = new ScheduledSubject <T>(getScheduler(tuple), null, new BehaviorSubject <T>(default(T)));
                mb[tuple] = new NotAWeakReference(ret);
            });

            return(ret);
        }
예제 #5
0
        public ReactiveCommand(IObservable <bool> canExecute, bool allowsConcurrentExecution, IScheduler scheduler, bool initialCondition = true)
        {
            canExecute                = canExecute ?? Observable.Return(true);
            defaultScheduler          = scheduler ?? RxApp.MainThreadScheduler;
            AllowsConcurrentExecution = allowsConcurrentExecution;

            canExecute = canExecute.Catch <bool, Exception>(ex => {
                exceptions.OnNext(ex);
                return(Observable.Empty <bool>());
            });

            ThrownExceptions = exceptions = new ScheduledSubject <Exception>(defaultScheduler, RxApp.DefaultExceptionHandler);

            var isExecuting = inflight
                              .Scan(0, (acc, x) => acc + (x ? 1 : -1))
                              .Select(x => x > 0)
                              .Publish(false)
                              .PermaRef()
                              .DistinctUntilChanged();

            IsExecuting = isExecuting.ObserveOn(defaultScheduler);

            var isBusy = allowsConcurrentExecution ? Observable.Return(false) : isExecuting;
            var canExecuteAndNotBusy = Observable.CombineLatest(canExecute, isBusy, (ce, b) => ce && !b);

            var canExecuteObs = canExecuteAndNotBusy
                                .Publish(initialCondition)
                                .RefCount();

            CanExecuteObservable = canExecuteObs
                                   .DistinctUntilChanged()
                                   .ObserveOn(defaultScheduler);

            innerDisp = canExecuteObs.Subscribe(x => {
                if (canExecuteLatest == x)
                {
                    return;
                }

                canExecuteLatest = x;
                defaultScheduler.Schedule(() => this.raiseCanExecuteChanged(EventArgs.Empty));
            }, exceptions.OnNext);
        }
예제 #6
0
        ISubject <T> setupSubjectIfNecessary <T>(string contract, IScheduler scheduler)
        {
            scheduler = scheduler ?? RxApp.DeferredScheduler;
            ISubject <T>      ret     = null;
            NotAWeakReference subjRef = null;

            withMessageBus(typeof(T), contract, (mb, tuple) => {
                if (mb.TryGetValue(tuple, out subjRef) && subjRef.IsAlive)
                {
                    ret = (ISubject <T>)subjRef.Target;
                    return;
                }

                ret       = new ScheduledSubject <T>(scheduler);
                mb[tuple] = new NotAWeakReference(ret);
            });

            return(ret);
        }
예제 #7
0
        /// <summary>
        /// Constructs an ObservableAsPropertyHelper object.
        /// </summary>
        /// <param name="observable">The Observable to base the property on.</param>
        /// <param name="onChanged">The action to take when the property
        /// changes, typically this will call the ViewModel's
        /// RaisePropertyChanged method.</param>
        /// <param name="initialValue">The initial value of the property.</param>
        /// <param name="scheduler">The scheduler that the notifications will be
        /// provided on - this should normally be a Dispatcher-based scheduler
        /// (and is by default)</param>
        public ObservableAsPropertyHelper(
            IObservable <T> observable,
            Action <T> onChanged,
            T initialValue       = default(T),
            IScheduler scheduler = null)
        {
            Contract.Requires(observable != null);
            Contract.Requires(onChanged != null);

            scheduler  = scheduler ?? RxApp.DeferredScheduler;
            _lastValue = initialValue;

            var subj      = new ScheduledSubject <T>(scheduler);
            var exSubject = new ScheduledSubject <Exception>(scheduler, RxApp.DefaultExceptionHandler);

            bool firedInitial = false;

            subj.Subscribe(x => {
                // Suppress a non-change between initialValue and the first value
                // from a Subscribe
                if (firedInitial && EqualityComparer <T> .Default.Equals(x, _lastValue))
                {
                    return;
                }

                this.Log().Debug("Property helper {0:X} changed", this.GetHashCode());
                _lastValue = x;
                onChanged(x);
                firedInitial = true;
            }, exSubject.OnNext);

            ThrownExceptions = exSubject;

            // Fire off an initial RaisePropertyChanged to make sure bindings
            // have a value
            subj.OnNext(initialValue);

            var src = observable.DistinctUntilChanged().Multicast(subj);

            _inner  = src.Connect();
            _source = src;
        }
예제 #8
0
        void setupRx(IEnumerable <T> List = null)
        {
            _BeforeItemsAdded   = new ScheduledSubject <T>(RxApp.DeferredScheduler);
            _BeforeItemsRemoved = new ScheduledSubject <T>(RxApp.DeferredScheduler);
            aboutToClear        = new Subject <int>();
            cleared             = new Subject <int>();

            if (List != null)
            {
                foreach (var v in List)
                {
                    this.Add(v);
                }
            }

            var ocChangedEvent = new Subject <NotifyCollectionChangedEventArgs>();

            CollectionChanged += (o, e) => ocChangedEvent.OnNext(e);

            _ItemsAdded = ocChangedEvent
                          .Where(x =>
                                 x.Action == NotifyCollectionChangedAction.Add ||
                                 x.Action == NotifyCollectionChangedAction.Replace)
                          .SelectMany(x =>
                                      (x.NewItems != null ? x.NewItems.OfType <T>() : Enumerable.Empty <T>())
                                      .ToObservable())
                          .Multicast(new ScheduledSubject <T>(RxApp.DeferredScheduler))
                          .PermaRef();

            _ItemsRemoved = ocChangedEvent
                            .Where(x =>
                                   x.Action == NotifyCollectionChangedAction.Remove ||
                                   x.Action == NotifyCollectionChangedAction.Replace)
                            .SelectMany(x =>
                                        (x.OldItems != null ? x.OldItems.OfType <T>() : Enumerable.Empty <T>())
                                        .ToObservable())
                            .Multicast(new ScheduledSubject <T>(RxApp.DeferredScheduler))
                            .PermaRef();

            _CollectionCountChanging = Observable.Merge(
                _BeforeItemsAdded.Select(_ => this.Count),
                _BeforeItemsRemoved.Select(_ => this.Count),
                aboutToClear
                );

            _CollectionCountChanged = Observable.Merge(
                _ItemsAdded.Select(_ => this.Count),
                _ItemsRemoved.Select(_ => this.Count),
                cleared
                );

            _ItemChanging = new ScheduledSubject <IObservedChange <T, object> >(RxApp.DeferredScheduler);
            _ItemChanged  = new ScheduledSubject <IObservedChange <T, object> >(RxApp.DeferredScheduler);

            // TODO: Fix up this selector nonsense once SL/WP7 gets Covariance
            _Changing = Observable.Merge(
                _BeforeItemsAdded.Select <T, IObservedChange <object, object> >(x =>
                                                                                new ObservedChange <object, object>()
            {
                PropertyName = "Items", Sender = this, Value = this
            }),
                _BeforeItemsRemoved.Select <T, IObservedChange <object, object> >(x =>
                                                                                  new ObservedChange <object, object>()
            {
                PropertyName = "Items", Sender = this, Value = this
            }),
                aboutToClear.Select <int, IObservedChange <object, object> >(x =>
                                                                             new ObservedChange <object, object>()
            {
                PropertyName = "Items", Sender = this, Value = this
            }),
                _ItemChanging.Select <IObservedChange <T, object>, IObservedChange <object, object> >(x =>
                                                                                                      new ObservedChange <object, object>()
            {
                PropertyName = x.PropertyName, Sender = x.Sender, Value = x.Value
            }));

            _Changed = Observable.Merge(
                _ItemsAdded.Select <T, IObservedChange <object, object> >(x =>
                                                                          new ObservedChange <object, object>()
            {
                PropertyName = "Items", Sender = this, Value = this
            }),
                _ItemsRemoved.Select <T, IObservedChange <object, object> >(x =>
                                                                            new ObservedChange <object, object>()
            {
                PropertyName = "Items", Sender = this, Value = this
            }),
                _ItemChanged.Select <IObservedChange <T, object>, IObservedChange <object, object> >(x =>
                                                                                                     new ObservedChange <object, object>()
            {
                PropertyName = x.PropertyName, Sender = x.Sender, Value = x.Value
            }));

            _ItemsAdded.Subscribe(x => {
                this.Log().Debug("Item Added to {0:X} - {1}", this.GetHashCode(), x);
                if (propertyChangeWatchers == null)
                {
                    return;
                }
                addItemToPropertyTracking(x);
            });

            _ItemsRemoved.Subscribe(x => {
                this.Log().Debug("Item removed from {0:X} - {1}", this.GetHashCode(), x);
                if (propertyChangeWatchers == null || !propertyChangeWatchers.ContainsKey(x))
                {
                    return;
                }

                removeItemFromPropertyTracking(x);
            });

            IsEmpty = CollectionCountChanged.Select(x => x == 0);

#if DEBUG
            _ItemChanged.Subscribe(x =>
                                   this.Log().Debug("Object {0} changed in collection {1:X}", x, this.GetHashCode()));
#endif
        }
예제 #9
0
        /// <summary>
        /// Initializes a new instance of the <see cref="CombinedReactiveCommand{TParam, TResult}"/> class.
        /// </summary>
        /// <param name="childCommands">The child commands which will be executed.</param>
        /// <param name="canExecute">A observable when the command can be executed.</param>
        /// <param name="outputScheduler">The scheduler where to dispatch the output from the command.</param>
        /// <exception cref="ArgumentNullException">Fires when required arguments are null.</exception>
        /// <exception cref="ArgumentException">Fires if the child commands container is empty.</exception>
        protected internal CombinedReactiveCommand(
            IEnumerable <ReactiveCommandBase <TParam, TResult> > childCommands,
            IObservable <bool> canExecute,
            IScheduler outputScheduler)
        {
            if (childCommands is null)
            {
                throw new ArgumentNullException(nameof(childCommands));
            }

            if (canExecute is null)
            {
                throw new ArgumentNullException(nameof(canExecute));
            }

            if (outputScheduler is null)
            {
                throw new ArgumentNullException(nameof(outputScheduler));
            }

            var childCommandsArray = childCommands.ToArray();

            if (childCommandsArray.Length == 0)
            {
                throw new ArgumentException("No child commands provided.", nameof(childCommands));
            }

            _exceptions = new ScheduledSubject <Exception>(outputScheduler, RxApp.DefaultExceptionHandler);

            var canChildrenExecute = childCommandsArray.Select(x => x.CanExecute)
                                     .CombineLatest()
                                     .Select(x => x.All(y => y));
            var combinedCanExecute = canExecute
                                     .Catch <bool, Exception>(ex =>
            {
                _exceptions.OnNext(ex);
                return(Observables.False);
            })
                                     .StartWith(false)
                                     .CombineLatest(canChildrenExecute, (ce, cce) => ce && cce)
                                     .DistinctUntilChanged()
                                     .Replay(1)
                                     .RefCount();

            _exceptionsSubscription = childCommandsArray.Select(x => x.ThrownExceptions)
                                      .Merge()
                                      .Subscribe(ex => _exceptions.OnNext(ex));

            _innerCommand = new ReactiveCommand <TParam, IList <TResult> >(
                param =>
                childCommandsArray
                .Select(x => x.Execute(param))
                .CombineLatest(),
                combinedCanExecute,
                outputScheduler);

            // we already handle exceptions on individual child commands above, but the same exception
            // will tick through innerCommand. Therefore, we need to ensure we ignore it or the default
            // handler will execute and the process will be torn down
            _innerCommand
            .ThrownExceptions
            .Subscribe();

            CanExecute.Subscribe(OnCanExecuteChanged);
        }