/// <summary>
        /// Prepares and sets up the observables and subjects used, particularly
        /// <see cref="ListChanges"/>, <see cref="INotifyObservableCountChanges.CountChanges"/> and <see cref="INotifyObserverExceptions.ObserverExceptions"/>.
        /// </summary>
        private void SetupObservablesAndObserversAndSubjects()
        {
            ListChangesObserver = _listChangesSubject.NotifyOn(Scheduler);

            // then connect to InnerList's ListChanged Event
            _innerListChangedRelevantListChangedEventsForwader = System.Reactive.Linq.Observable.FromEventPattern <ListChangedEventHandler, ListChangedEventArgs>(
                handler => InnerList.ListChanged += handler,
                handler => InnerList.ListChanged -= handler)
                                                                 .TakeWhile(_ => !IsDisposing && !IsDisposed)
                                                                 .SkipContinuouslyWhile(_ => !IsTrackingChanges)
                                                                 .Where(eventPattern => eventPattern?.EventArgs != null)
                                                                 .Select(eventPattern => eventPattern.EventArgs.ToObservableListChange(InnerList, this))
                                                                 .ObserveOn(Scheduler)
                                                                 .Subscribe(
                NotifySubscribersAboutListChanges,
                exception =>
            {
                // ToDo: at this point this instance is practically doomed / no longer forwarding any events & therefore further usage of the instance itself should be prevented, or the observable stream should re-connect/signal-and-swallow exceptions. Either way.. not ideal.
                var observerException = new ObserverException(
                    $"An error occured notifying observers of this {this.GetType().Name} - consistency and future notifications are no longer guaranteed.",
                    exception);

                ObserverExceptionsObserver.OnNext(observerException);
            });
        }
Example #2
0
        /// <summary>
        /// Prepares and sets up the observables and subjects used, particularly
        /// <see cref="_collectionChangesSubject"/>, <see cref="_countChangesSubject"/> and <see cref="_observerExceptionsSubject"/>
        /// but also internally used RX subscriptions for <see cref="IBindingList.ListChanged"/> and somewhat hack-ish
        /// 'Count' and 'Items[]' <see cref="INotifyPropertyChanged"/> events on <see cref="CountChanges"/> and <see cref="CollectionChanges"/>
        /// occurrences (for WPF / Binding)
        /// </summary>
        private void SetupObservablesAndObserversAndSubjects()
        {
            ObserverExceptionsObserver = _observerExceptionsSubject.NotifyOn(Scheduler);
            CollectionChangesObserver  = _collectionChangesSubject.NotifyOn(Scheduler);
            CountChangesObserver       = _countChangesSubject.NotifyOn(Scheduler);

            // then connect to InnerList's ListChanged Event
            _innerListChangedRelevantCollectionChangedEventsForwader = System.Reactive.Linq.Observable.FromEventPattern <ListChangedEventHandler, ListChangedEventArgs>(
                handler => InnerList.ListChanged += handler,
                handler => InnerList.ListChanged -= handler)
                                                                       .TakeWhile(_ => !IsDisposing && !IsDisposed)
                                                                       .SkipContinuouslyWhile(_ => !IsTrackingChanges)
                                                                       .Where(eventPattern => eventPattern?.EventArgs != null)
                                                                       .SelectMany(eventPattern => eventPattern.EventArgs.ToObservableCollectionChanges(InnerList))
                                                                       .ObserveOn(Scheduler)
                                                                       .Subscribe(
                NotifyObserversAboutCollectionChanges,
                exception =>
            {
                // ToDo: at this point this instance is practically doomed / no longer forwarding any events & therefore further usage of the instance itself should be prevented, or the observable stream should re-connect/signal-and-swallow exceptions. Either way.. not ideal.
                var observerException = new ObserverException(
                    $"An error occured notifying observers of this {this.GetType().Name} - consistency and future notifications are no longer guaranteed.",
                    exception);
                ObserverExceptionsObserver.OnNext(observerException);
            });


            // 'Count' and 'Item[]' PropertyChanged events are used by WPF typically via / for ObservableCollections, see
            // http://referencesource.microsoft.com/#System/compmod/system/collections/objectmodel/observablecollection.cs,421
            _countChangesCountPropertyChangedForwarder = CountChanges
                                                         .ObserveOn(Scheduler)
                                                         .Subscribe(_ => RaisePropertyChanged(nameof(Count)));

            _collectionChangesItemIndexerPropertyChangedForwarder = CollectionChanges
                                                                    .ObserveOn(Scheduler)
                                                                    .Subscribe(_ => RaisePropertyChanged(ItemIndexerName));
        }
Example #3
0
        /// <summary>
        ///     Notifies all <see cref="CollectionChanges" /> and <see cref="Resets" /> subscribers and
        ///     raises the (observable)collection changed events.
        /// </summary>
        /// <param name="observableCollectionChange">The observable collection change.</param>
        protected virtual void NotifyObserversAboutCollectionChanges(IObservableCollectionChange <T> observableCollectionChange)
        {
            if (observableCollectionChange == null)
            {
                throw new ArgumentNullException(nameof(observableCollectionChange));
            }

            CheckForAndThrowIfDisposed();

            // go ahead and check whether a Reset or item add, -change, -move or -remove shall be signaled
            // .. based on the ThresholdAmountWhenChangesAreNotifiedAsReset value
            var actualObservableCollectionChange =
                (observableCollectionChange.ChangeType == ObservableCollectionChangeType.Reset ||
                 IsItemsChangedAmountGreaterThanResetThreshold(1, ThresholdAmountWhenChangesAreNotifiedAsReset))
                    ? ObservableCollectionChange <T> .Reset
                    : observableCollectionChange;

            // raise events and notify about collection changes

            if (actualObservableCollectionChange.ChangeType == ObservableCollectionChangeType.ItemAdded ||
                actualObservableCollectionChange.ChangeType == ObservableCollectionChangeType.ItemRemoved ||
                actualObservableCollectionChange.ChangeType == ObservableCollectionChangeType.Reset)
            {
                try
                {
                    CountChangesObserver.OnNext(Count);
                }
                catch (Exception exception)
                {
                    var observerException = new ObserverException(
                        $"An error occured notifying {nameof(CountChanges)} Observers of this {this.GetType().Name}.",
                        exception);

                    ObserverExceptionsObserver.OnNext(observerException);

                    if (observerException.Handled == false)
                    {
                        throw;
                    }
                }
            }

            try
            {
                CollectionChangesObserver.OnNext(actualObservableCollectionChange);
            }
            catch (Exception exception)
            {
                var observerException = new ObserverException(
                    $"An error occured notifying {nameof(CollectionChanges)} Observers of this {this.GetType().Name}.",
                    exception);

                ObserverExceptionsObserver.OnNext(observerException);

                if (observerException.Handled == false)
                {
                    throw;
                }
            }

            try
            {
                RaiseCollectionChanged(actualObservableCollectionChange.ToNotifyCollectionChangedEventArgs());
            }
            catch (Exception exception)
            {
                var observerException = new ObserverException(
                    $"An error occured notifying CollectionChanged Subscribers of this {this.GetType().Name}.",
                    exception);

                ObserverExceptionsObserver.OnNext(observerException);

                if (observerException.Handled == false)
                {
                    throw;
                }
            }
        }
        /// <summary>
        ///     Notifies all <see cref="ListChanges" /> and <see cref="INotifyObservableResets.Resets" /> subscribers and
        ///     raises the (observable)collection changed events.
        /// </summary>
        /// <param name="observableListChange">The observable list change.</param>
        protected virtual void NotifySubscribersAboutListChanges(IObservableListChange <T> observableListChange)
        {
            // This is similar to what ObservableCollection implements via its NotifyObserversAboutCollectionChanges method,
            // however:
            // - no need to handle count-relevant changes because the underlying ObservableCollection takes care of this
            // - no (extra) (Raise)CollectionChanged call here, again.. already done by the ObservableCollection
            // - however as 'Move's are only possible for / with ObservableLists, we also raise a PropertyChangedEvent for 'Item[]' (for wpf) in case of a item move(s)

            if (observableListChange == null)
            {
                throw new ArgumentNullException(nameof(observableListChange));
            }

            CheckForAndThrowIfDisposed();

            // go ahead and check whether a Reset or item add, -change, -move or -remove shall be signaled
            // .. based on the ThresholdAmountWhenChangesAreNotifiedAsReset value
            var actualObservableListChange =
                (observableListChange.ChangeType == ObservableListChangeType.Reset ||
                 IsItemsChangedAmountGreaterThanResetThreshold(1, ThresholdAmountWhenChangesAreNotifiedAsReset))
                    ? ObservableListChange <T> .Reset(this)
                    : observableListChange;

            // raise events and notify about list changes
            try
            {
                ListChangesObserver.OnNext(actualObservableListChange);
            }
            catch (Exception exception)
            {
                var observerException = new ObserverException(
                    $"An error occured notifying {nameof(ListChanges)} observers of this {this.GetType().Name}.",
                    exception);

                ObserverExceptionsObserver.OnNext(observerException);

                if (observerException.Handled == false)
                {
                    throw;
                }
            }

            if (actualObservableListChange.ChangeType == ObservableListChangeType.ItemMoved)
            {
                try
                {
                    RaisePropertyChanged(ItemIndexerName);
                }
                catch (Exception exception)
                {
                    var observerException = new ObserverException(
                        $"An error occured notifying {nameof(PropertyChanged)} subscribers of this {this.GetType().Name}.",
                        exception);

                    ObserverExceptionsObserver.OnNext(observerException);

                    if (observerException.Handled == false)
                    {
                        throw;
                    }
                }
            }
        }