From b3180bc891c49a880d224cabe73f9524e5b8f8c3 Mon Sep 17 00:00:00 2001 From: Chris Copsey <150505+ccopsey@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:30:21 +0100 Subject: [PATCH] Adds optional "useReplaceForUpdates" to SortedObservableCollectionAdaptor (#726) --- ...ervableCollectionBindCacheSortedFixture.cs | 44 ++++++++++++++----- .../SortedObservableCollectionAdaptor.cs | 9 ++-- src/DynamicData/Cache/ObservableCacheEx.cs | 5 ++- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheSortedFixture.cs b/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheSortedFixture.cs index 2178b3777..4566d7a4a 100644 --- a/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheSortedFixture.cs +++ b/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheSortedFixture.cs @@ -196,22 +196,42 @@ public void UpdateToSourceSendsRemoveAndAddIfSortingIsAffected() [Fact] public void UpdateToSourceSendsReplaceIfSortingIsNotAffected() { - var person1 = new Person("Adult1", 10); - var person2 = new Person("Adult2", 11); - - NotifyCollectionChangedAction action = default; - _source.AddOrUpdate(person1); - _source.AddOrUpdate(person2); + RunTest(true); + RunTest(false); - var person2Updated = new Person("Adult2", 12); - using (_collection.ObserveCollectionChanges().Select(change => change.EventArgs.Action).Subscribe(act => action = act)) + void RunTest(bool useReplace) { - _source.AddOrUpdate(person2Updated); - } + var collection = new ObservableCollectionExtended(); + + using var source = new SourceCache(p => p.Name); + using var binder = source.Connect().Sort(_comparer, resetThreshold: 25).Bind(collection, new ObservableCollectionAdaptor(useReplaceForUpdates: useReplace)).Subscribe(); + + var person1 = new Person("Adult1", 10); + var person2 = new Person("Adult2", 11); + + NotifyCollectionChangedAction action = default; + source.AddOrUpdate(person1); + source.AddOrUpdate(person2); + + var person2Updated = new Person("Adult2", 12); - action.Should().Be(NotifyCollectionChangedAction.Replace, "The notification type should be Replace"); - _collection.Should().Equal(person1, person2Updated); + using (collection.ObserveCollectionChanges().Select(x => x.EventArgs.Action).Subscribe(updateType => action = updateType)) + { + source.AddOrUpdate(person2Updated); + } + + if (useReplace) + { + action.Should().Be(NotifyCollectionChangedAction.Replace, "The notification type should be Replace"); + } + else + { + action.Should().Be(NotifyCollectionChangedAction.Add, "The notification type should be Add"); + } + + collection.Should().Equal(person1, person2Updated); + } } [Fact] diff --git a/src/DynamicData/Binding/SortedObservableCollectionAdaptor.cs b/src/DynamicData/Binding/SortedObservableCollectionAdaptor.cs index 7b62333b9..b76e63034 100644 --- a/src/DynamicData/Binding/SortedObservableCollectionAdaptor.cs +++ b/src/DynamicData/Binding/SortedObservableCollectionAdaptor.cs @@ -18,14 +18,17 @@ public class SortedObservableCollectionAdaptor : ISortedObservabl where TKey : notnull { private readonly int _refreshThreshold; + private readonly bool _useReplaceForUpdates; /// /// Initializes a new instance of the class. /// /// The number of changes before a Reset event is used. - public SortedObservableCollectionAdaptor(int refreshThreshold = 25) + /// Use replace instead of remove / add for updates. + public SortedObservableCollectionAdaptor(int refreshThreshold = 25, bool useReplaceForUpdates = true) { _refreshThreshold = refreshThreshold; + _useReplaceForUpdates = useReplaceForUpdates; } /// @@ -89,7 +92,7 @@ public void Adapt(ISortedChangeSet changes, IObservableCollection } } - private static void DoUpdate(ISortedChangeSet updates, IObservableCollection list) + private void DoUpdate(ISortedChangeSet updates, IObservableCollection list) { foreach (var update in updates) { @@ -108,7 +111,7 @@ private static void DoUpdate(ISortedChangeSet updates, IObservabl break; case ChangeReason.Update: - if (update.PreviousIndex != update.CurrentIndex) + if (!_useReplaceForUpdates || update.PreviousIndex != update.CurrentIndex) { list.RemoveAt(update.PreviousIndex); list.Insert(update.CurrentIndex, update.Current); diff --git a/src/DynamicData/Cache/ObservableCacheEx.cs b/src/DynamicData/Cache/ObservableCacheEx.cs index b4a9ff1bd..16ed90801 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.cs @@ -745,10 +745,11 @@ public static IObservable> Bind(t /// The source. /// The resulting read only observable collection. /// The number of changes before a reset event is called on the observable collection. + /// Use replace instead of remove / add for updates. NB: Some platforms to not support replace notifications for binding. /// Specify an adaptor to change the algorithm to update the target collection. /// An observable which will emit change sets. /// source. - public static IObservable> Bind(this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25, ISortedObservableCollectionAdaptor? adaptor = null) + public static IObservable> Bind(this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25, bool useReplaceForUpdates = true, ISortedObservableCollectionAdaptor? adaptor = null) where TObject : notnull where TKey : notnull { @@ -759,7 +760,7 @@ public static IObservable> Bind(this IO var target = new ObservableCollectionExtended(); var result = new ReadOnlyObservableCollection(target); - var updater = adaptor ?? new SortedObservableCollectionAdaptor(resetThreshold); + var updater = adaptor ?? new SortedObservableCollectionAdaptor(resetThreshold, useReplaceForUpdates); readOnlyObservableCollection = result; return source.Bind(target, updater); }