/// <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(); } }
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); }
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); }
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); }
/// <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; }
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 }
/// <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); }