/// <summary> /// Forwards the <paramref name="source" /> changes to the <paramref name="target" />. /// </summary> /// <typeparam name="TKey">The type of the key.</typeparam> /// <typeparam name="TValue">The type of the value.</typeparam> /// <param name="source">The source observable list.</param> /// <param name="target">The target <see cref="IEnhancedBindingList{T}" />.</param> /// <param name="includeItemChanges"> /// if set to <c>true</c> individual items' changes will be propagated to the /// <paramref name="target" />. /// </param> /// <param name="scheduler">The scheduler to schedule notifications and changes on.</param> /// <returns> /// An <see cref="IDisposable" /> which will forward the changes to the <paramref name="target" /> as long as /// <see cref="IDisposable.Dispose" /> hasn't been called. /// </returns> public static IDisposable ForwardDictionaryChangesTo <TKey, TValue>( this IObservableCache <TKey, TValue> source, IEnhancedBindingList <TValue> target, bool includeItemChanges = false, IScheduler scheduler = null) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (target == null) { throw new ArgumentNullException(nameof(target)); } var sourceObservable = scheduler != null ? source.Changes.ObserveOn(scheduler) : source.Changes; return(sourceObservable.Subscribe(cacheChange => { switch (cacheChange.ChangeType) { case ObservableCacheChangeType.ItemAdded: { target.Add(cacheChange.Value); break; } case ObservableCacheChangeType.ItemKeyChanged: { // nothing to do here break; } case ObservableCacheChangeType.ItemValueChanged: { if (includeItemChanges) { // check whether target list contains the moved element at its expected index position var targetIndex = target.IndexOf(cacheChange.Value); if (targetIndex == -1) { return; } target.ResetItem(targetIndex); } break; } case ObservableCacheChangeType.ItemValueReplaced: { if (includeItemChanges) { if (target.Contains(cacheChange.OldValue)) { target.Remove(cacheChange.OldValue); } var newValueTargetIndex = target.IndexOf(cacheChange.Value); if (newValueTargetIndex != -1) { target.ResetItem(newValueTargetIndex); } else { target.Add(cacheChange.Value); } } break; } case ObservableCacheChangeType.ItemRemoved: { // check whether target list contains the removed item, and delete if so if (target.Contains(cacheChange.Value)) { target.Remove(cacheChange.Value); } break; } case ObservableCacheChangeType.Reset: { var originalBindingRaiseListChangedEvents = target.RaiseListChangedEvents; try { target.RaiseListChangedEvents = false; ((ICollection <TValue>)target).Clear(); target.AddRange(source.CurrentValues); } finally { target.RaiseListChangedEvents = originalBindingRaiseListChangedEvents; if (originalBindingRaiseListChangedEvents) { target.ResetBindings(); } } break; } default: throw new ArgumentOutOfRangeException(nameof(cacheChange), $"Only {ObservableDictionaryChangeType.ItemAdded}, {ObservableDictionaryChangeType.ItemKeyChanged}, {ObservableDictionaryChangeType.ItemValueChanged}, {ObservableDictionaryChangeType.ItemValueReplaced}, {ObservableDictionaryChangeType.ItemRemoved} and {ObservableDictionaryChangeType.Reset} are supported."); } })); }
/// <summary> /// Forwards the <paramref name="sourceObservable" /> changes to the <paramref name="target" />. /// </summary> /// <typeparam name="T">The type of the list item(s)</typeparam> /// <param name="sourceObservable">The source observable.</param> /// <param name="target">The target binding list.</param> /// <param name="includeItemChanges">if set to <c>true</c> individual items' changes will be propagated to the /// <paramref name="target" /> via replacing the item completely.</param> /// <param name="includeMoves">if set to <c>true</c> move operations will be replicated to the <paramref name="target" />.</param> /// <param name="addRangePredicateForResets">This filter predicate tests which elements of the source <see cref="IObservableListChange{T}"/> to add /// whenever a <see cref="ObservableListChangeType.Reset"/> is received. A reset is forwarded by clearing the <paramref name="target"/> completely and re-filling it with /// the source's values, and this predicate determines which ones are added. If no filter predicate is provided, all source values will be re-added to the <paramref name="target"/>.</param> /// <returns></returns> public static IDisposable ForwardListChangesTo <T>( this IObservable <IObservableListChange <T> > sourceObservable, IEnhancedBindingList <T> target, bool includeItemChanges = true, bool includeMoves = false, Func <T, bool> addRangePredicateForResets = null) { if (sourceObservable == null) { throw new ArgumentNullException(nameof(sourceObservable)); } if (target == null) { throw new ArgumentNullException(nameof(target)); } if (addRangePredicateForResets == null) { addRangePredicateForResets = _ => true; } return(sourceObservable.Subscribe(observableListChange => { switch (observableListChange.ChangeType) { case ObservableListChangeType.ItemAdded: { if (includeMoves) { target.Insert(observableListChange.Index, observableListChange.Item); } else { target.Add(observableListChange.Item); } break; } case ObservableListChangeType.ItemChanged: { if (includeItemChanges) { // check whether target list contains the moved element at its expected index position var targetIndex = target.IndexOf(observableListChange.Item); if (targetIndex == -1) { return; } target.ResetItem(targetIndex); } break; } case ObservableListChangeType.ItemMoved: { if (includeMoves) { // check whether target list contains the moved element at its expected index position if (target.IndexOf(observableListChange.Item) != observableListChange.OldIndex) { throw new InvalidOperationException($"The source and and target lists are no longer in sync: target has a diffent item at index position {observableListChange.OldIndex} than expected."); } target.Move(observableListChange.Item, observableListChange.Index); } break; } case ObservableListChangeType.ItemRemoved: { // check whether target list contains the removed item, and delete if so if (target.Contains(observableListChange.Item)) { target.Remove(observableListChange.Item); } break; } case ObservableListChangeType.Reset: { var originalBindingRaiseListChangedEvents = target.RaiseListChangedEvents; try { target.RaiseListChangedEvents = false; target.Clear(); target.AddRange(observableListChange.List.Where(addRangePredicateForResets)); } finally { target.RaiseListChangedEvents = originalBindingRaiseListChangedEvents; if (originalBindingRaiseListChangedEvents) { target.ResetBindings(); } } break; } default: break; } })); }
/// <summary> /// Forwards the <paramref name="sourceObservable" /> changes to the <paramref name="target" />. /// </summary> /// <typeparam name="TKey">The type of the key.</typeparam> /// <typeparam name="TValue">The type of the value.</typeparam> /// <param name="sourceObservable">The source observable.</param> /// <param name="target">The target <see cref="IEnhancedBindingList{TValue}" />.</param> /// <param name="includeItemChanges">if set to <c>true</c> individual items' changes will be propagated to the <paramref name="target" />.</param> /// <param name="addRangePredicateForResets">This filter predicate tests which elements of the source <see cref="IObservableDictionary{TKey,TValue}" /> to add /// whenever a <see cref="ObservableDictionaryChangeType.Reset" /> is received. A reset is forwarded by clearing the <paramref name="target" /> completely and re-filling it with /// the source's values, and this predicate determines which ones are added. If no filter predicate is provided, all source values will be re-added to the <paramref name="target" />.</param> /// <param name="addDistinctValuesOnResetOnly">if set to <c>true</c> only distinct values will be re-added on <see cref="ObservableDictionaryChangeType.Reset" /> changes.</param> /// <param name="valueComparerForResets">The value equality comparer to use for reset changes and if <paramref name="valueComparerForResets"/> is set to [true]. If none is provided, the default one for the value type will be used</param> /// <returns> /// An <see cref="IDisposable" /> which will forward the changes to the <paramref name="target" /> as long as <see cref="IDisposable.Dispose" /> hasn't been called. /// </returns> /// <exception cref="System.ArgumentNullException"> /// </exception> public static IDisposable ForwardDictionaryChangesTo <TKey, TValue>( this IObservable <IObservableDictionaryChange <TKey, TValue> > sourceObservable, IEnhancedBindingList <TValue> target, bool includeItemChanges = false, Func <KeyValuePair <TKey, TValue>, bool> addRangePredicateForResets = null, bool addDistinctValuesOnResetOnly = true, IEqualityComparer <TValue> valueComparerForResets = null) { if (sourceObservable == null) { throw new ArgumentNullException(nameof(sourceObservable)); } if (target == null) { throw new ArgumentNullException(nameof(target)); } if (addRangePredicateForResets == null) { addRangePredicateForResets = _ => true; } if (addDistinctValuesOnResetOnly == true && valueComparerForResets == null) { valueComparerForResets = EqualityComparer <TValue> .Default; } return(sourceObservable.Subscribe(dictionaryChange => { switch (dictionaryChange.ChangeType) { case ObservableDictionaryChangeType.ItemAdded: { target.Add(dictionaryChange.Value); break; } case ObservableDictionaryChangeType.ItemKeyChanged: { // nothing to do here break; } case ObservableDictionaryChangeType.ItemValueChanged: { if (includeItemChanges) { // check whether target list contains the moved element at its expected index position var targetIndex = target.IndexOf(dictionaryChange.Value); if (targetIndex == -1) { return; } target.ResetItem(targetIndex); } break; } case ObservableDictionaryChangeType.ItemValueReplaced: { if (includeItemChanges) { if (target.Contains(dictionaryChange.OldValue)) { target.Remove(dictionaryChange.OldValue); } var newValueTargetIndex = target.IndexOf(dictionaryChange.Value); if (newValueTargetIndex != -1) { target.ResetItem(newValueTargetIndex); } else { target.Add(dictionaryChange.Value); } } break; } case ObservableDictionaryChangeType.ItemRemoved: { // check whether target list contains the removed item, and delete if so if (target.Contains(dictionaryChange.Value)) { target.Remove(dictionaryChange.Value); } break; } case ObservableDictionaryChangeType.Reset: { var originalBindingRaiseListChangedEvents = target.RaiseListChangedEvents; try { target.RaiseListChangedEvents = false; target.Clear(); var rangeofValuesToAdd = dictionaryChange.Dictionary .Where(keyValuePair => addRangePredicateForResets(keyValuePair)) .Select(kvp => kvp.Value); if (addDistinctValuesOnResetOnly == true) { rangeofValuesToAdd = rangeofValuesToAdd.Distinct(valueComparerForResets); } target.AddRange(rangeofValuesToAdd); } finally { target.RaiseListChangedEvents = originalBindingRaiseListChangedEvents; if (originalBindingRaiseListChangedEvents) { target.ResetBindings(); } } break; } default: throw new ArgumentOutOfRangeException(nameof(dictionaryChange), $"Only {ObservableDictionaryChangeType.ItemAdded}, {ObservableDictionaryChangeType.ItemKeyChanged}, {ObservableDictionaryChangeType.ItemValueChanged}, {ObservableDictionaryChangeType.ItemValueReplaced}, {ObservableDictionaryChangeType.ItemRemoved} and {ObservableDictionaryChangeType.Reset} are supported."); } })); }