diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index 564fbcd..7ac2912 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -1,24 +1,66 @@ -using ObservableCollections; -using System; -using System.Collections.Specialized; +using System; +using System.Linq; +using ObservableCollections; + +var models = new ObservableList(Enumerable.Range(0, 10)); +var viewModels = models.CreateView(x => new ViewModel +{ + Id = x, + Value = "@" + x +}); + +viewModels.AttachFilter(new HogeFilter(), true); -// Basic sample, use like ObservableCollection. -// CollectionChanged observes all collection modification -var list = new ObservableList(); -var view = list.CreateView(x => x.ToString() + "$"); +models.Add(100); -list.Add(10); -list.Add(20); -list.AddRange(new[] { 30, 40, 50 }); -list[1] = 60; -list.RemoveAt(3); +foreach (var (x, xs) in viewModels) +{ + System.Console.WriteLine(xs.Value); +} -foreach (var (_, v) in view) +class ViewModel { - // 10$, 60$, 30$, 50$ - Console.WriteLine(v); + public int Id { get; set; } + public string Value { get; set; } } -// Dispose view is unsubscribe collection changed event. -view.Dispose(); \ No newline at end of file +class HogeFilter : ISynchronizedViewFilter +{ + public bool IsMatch(int value, ViewModel view) + { + return value % 2 == 0; + } + + public void WhenTrue(int value, ViewModel view) + { + view.Value = $"@{value} (even)"; + } + + public void WhenFalse(int value, ViewModel view) + { + view.Value = $"@{value} (odd)"; + } + + public void OnCollectionChanged( + ChangedKind changedKind, + int value, + ViewModel view, + in NotifyCollectionChangedEventArgs eventArgs) + { + switch (changedKind) + { + case ChangedKind.Add: + view.Value += " Add"; + break; + case ChangedKind.Remove: + view.Value += " Remove"; + break; + case ChangedKind.Move: + view.Value += $" Move {eventArgs.OldStartingIndex} {eventArgs.NewStartingIndex}"; + break; + default: + throw new ArgumentOutOfRangeException(nameof(changedKind), changedKind, null); + } + } +} \ No newline at end of file diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/IObservableCollection.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/IObservableCollection.cs index 8389311..f1251dd 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/IObservableCollection.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/IObservableCollection.cs @@ -28,7 +28,7 @@ public interface ISynchronizedView : IReadOnlyCollection<(T Value, TVi event NotifyCollectionChangedEventHandler RoutingCollectionChanged; event Action CollectionStateChanged; - void AttachFilter(ISynchronizedViewFilter filter); + void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForInitialElements = false); void ResetFilter(Action resetAction); INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged(); } diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/CloneCollection.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/CloneCollection.cs index 0c830f6..1e3193b 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/CloneCollection.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/CloneCollection.cs @@ -49,7 +49,7 @@ public CloneCollection(IEnumerable source) } else { - var array = ArrayPool.Shared.Rent(count); + var array = ArrayPool.Shared.Rent(16); var i = 0; foreach (var item in source) @@ -75,8 +75,8 @@ static void TryEnsureCapacity(ref T[] array, int index) if (array.Length == index) { ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); + array = ArrayPool.Shared.Rent(index * 2); } - array = ArrayPool.Shared.Rent(index * 2); } public void Dispose() diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/FreezedView.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/FreezedView.cs index 710428c..1c0c1c5 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/FreezedView.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/FreezedView.cs @@ -38,14 +38,22 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; - foreach (var (value, view) in list) + for (var i = 0; i < list.Count; i++) { - filter.InvokeOnAttach(value, view); + var (value, view) = list[i]; + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } @@ -133,14 +141,22 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; - foreach (var (value, view) in array) + for (var i = 0; i < array.Length; i++) { - filter.InvokeOnAttach(value, view); + var (value, view) = array[i]; + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } @@ -178,7 +194,6 @@ public void ResetFilter(Action resetAction) public void Dispose() { - } public void Sort(IComparer comparer) diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/NotifyCollectionChangedSynchronizedView.cs index e042955..8b2f602 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -55,7 +55,7 @@ public event NotifyCollectionChangedEventHandler RoutingCollectionChanged remove { parent.RoutingCollectionChanged -= value; } } - public void AttachFilter(ISynchronizedViewFilter filter) => parent.AttachFilter(filter); + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) => parent.AttachFilter(filter, invokeAddEventForCurrentElements); public void ResetFilter(Action resetAction) => parent.ResetFilter(resetAction); public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() => this; public void Dispose() diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedView.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedView.cs index 43b8a98..24b37e2 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedView.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedView.cs @@ -51,14 +51,21 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; foreach (var (_, (value, view)) in dict) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, -1)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedViewViewComparer.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedViewViewComparer.cs index 35b75cd..be1699b 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedViewViewComparer.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedViewViewComparer.cs @@ -55,14 +55,21 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; foreach (var (_, (value, view)) in dict) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, -1)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableDictionary.Views.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableDictionary.Views.cs index e3c63ef..c4421cd 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableDictionary.Views.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableDictionary.Views.cs @@ -55,14 +55,23 @@ public void Dispose() this.source.CollectionChanged -= SourceCollectionChanged; } - public void AttachFilter(ISynchronizedViewFilter, TView> filter) + public void AttachFilter(ISynchronizedViewFilter, TView> filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; foreach (var v in dict) { - filter.InvokeOnAttach(new KeyValuePair(v.Key, v.Value.Item1), v.Value.Item2); + var value = new KeyValuePair(v.Key, v.Value.Item1); + var view = v.Value.Item2; + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs>.Add(value, -1)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } @@ -118,44 +127,44 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs(e.NewItem.Key, e.NewItem.Value), v, e); - } + { + var v = selector(e.NewItem); + dict.Add(e.NewItem.Key, (e.NewItem.Value, v)); + filter.InvokeOnAdd(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v, e); + } break; case NotifyCollectionChangedAction.Remove: + { + if (dict.Remove(e.OldItem.Key, out var v)) { - if (dict.Remove(e.OldItem.Key, out var v)) - { - filter.InvokeOnRemove((new KeyValuePair(e.OldItem.Key, v.Item1), v.Item2), e); - } + filter.InvokeOnRemove((new KeyValuePair(e.OldItem.Key, v.Item1), v.Item2), e); } + } break; case NotifyCollectionChangedAction.Replace: + { + if (dict.Remove(e.OldItem.Key, out var oldView)) { - if (dict.Remove(e.OldItem.Key, out var oldView)) - { - filter.InvokeOnRemove((new KeyValuePair(e.OldItem.Key, oldView.Item1), oldView.Item2), e); - } - - var v = selector(e.NewItem); - dict[e.NewItem.Key] = (e.NewItem.Value, v); - filter.InvokeOnAdd(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v, e); + filter.InvokeOnRemove((new KeyValuePair(e.OldItem.Key, oldView.Item1), oldView.Item2), e); } + + var v = selector(e.NewItem); + dict[e.NewItem.Key] = (e.NewItem.Value, v); + filter.InvokeOnAdd(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v, e); + } break; case NotifyCollectionChangedAction.Reset: + { + if (!filter.IsNullFilter()) { - if (!filter.IsNullFilter()) + foreach (var item in dict) { - foreach (var item in dict) - { - filter.InvokeOnRemove((new KeyValuePair(item.Key, item.Value.Item1), item.Value.Item2), e); - } + filter.InvokeOnRemove((new KeyValuePair(item.Key, item.Value.Item1), item.Value.Item2), e); } - - dict.Clear(); } + + dict.Clear(); + } break; case NotifyCollectionChangedAction.Move: // ObservableDictionary have no Move operation. default: @@ -168,4 +177,4 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; foreach (var (_, (value, view)) in dict) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd((value, view), NotifyCollectionChangedEventArgs.Add(value, -1)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableList.Views.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableList.Views.cs index 69b832d..8ea8aa1 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableList.Views.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableList.Views.cs @@ -53,14 +53,23 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; - foreach (var (value, view) in list) + for (var i = 0; i < list.Count; i++) { - filter.InvokeOnAttach(value, view); + var (value, view) = list[i]; + if (invokeAddEventForCurrentElements) + { + var eventArgs = NotifyCollectionChangedEventArgs.Add(value, i); + filter.InvokeOnAdd(value, view, eventArgs); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } @@ -193,24 +202,24 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) break; case NotifyCollectionChangedAction.Replace: // ObservableList does not support replace range - { - var v = (e.NewItem, selector(e.NewItem)); + { + var v = (e.NewItem, selector(e.NewItem)); - var oldItem = list[e.NewStartingIndex]; - list[e.NewStartingIndex] = v; + var oldItem = list[e.NewStartingIndex]; + list[e.NewStartingIndex] = v; - filter.InvokeOnRemove(oldItem, e); - filter.InvokeOnAdd(v, e); - break; - } + filter.InvokeOnRemove(oldItem, e); + filter.InvokeOnAdd(v, e); + break; + } case NotifyCollectionChangedAction.Move: - { - var removeItem = list[e.OldStartingIndex]; - list.RemoveAt(e.OldStartingIndex); - list.Insert(e.NewStartingIndex, removeItem); + { + var removeItem = list[e.OldStartingIndex]; + list.RemoveAt(e.OldStartingIndex); + list.Insert(e.NewStartingIndex, removeItem); - filter.InvokeOnMove(removeItem, e); - } + filter.InvokeOnMove(removeItem, e); + } break; case NotifyCollectionChangedAction.Reset: if (!filter.IsNullFilter()) diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableQueue.Views.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableQueue.Views.cs index 92e4443..4bf8bc2 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableQueue.Views.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableQueue.Views.cs @@ -53,14 +53,23 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; + var i = 0; foreach (var (value, view) in queue) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } + i++; } } } diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableRingBuffer.Views.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableRingBuffer.Views.cs index 745c916..e5ef2c4 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableRingBuffer.Views.cs @@ -54,14 +54,22 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; - foreach (var (value, view) in ringBuffer) + for (var i = 0; i < ringBuffer.Count; i++) { - filter.InvokeOnAttach(value, view); + var (value, view) = ringBuffer[i]; + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableStack.Views.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableStack.Views.cs index 9fba638..4527591 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableStack.Views.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/ObservableStack.Views.cs @@ -53,14 +53,23 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; + var i = 0; foreach (var (value, view) in stack) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } + i++; } } } diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Shims/Nullables.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Shims/Nullables.cs index eba1209..9b745a3 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Shims/Nullables.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Shims/Nullables.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -#if (NETSTANDARD2_0 || NET_STANDARD_2_0 || NET_4_6) && !UNITY_2021_1_OR_NEWER +#if NETSTANDARD2_0 || NET_STANDARD_2_0 || NET_4_6 namespace System.Diagnostics.CodeAnalysis { diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index ef21cac..abc38b5 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -28,7 +28,7 @@ public interface ISynchronizedView : IReadOnlyCollection<(T Value, TVi event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; event Action? CollectionStateChanged; - void AttachFilter(ISynchronizedViewFilter filter); + void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForInitialElements = false); void ResetFilter(Action? resetAction); INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged(); } diff --git a/src/ObservableCollections/Internal/FreezedView.cs b/src/ObservableCollections/Internal/FreezedView.cs index 95f7748..f9cd98a 100644 --- a/src/ObservableCollections/Internal/FreezedView.cs +++ b/src/ObservableCollections/Internal/FreezedView.cs @@ -38,14 +38,22 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; - foreach (var (value, view) in list) + for (var i = 0; i < list.Count; i++) { - filter.InvokeOnAttach(value, view); + var (value, view) = list[i]; + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } @@ -133,14 +141,22 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; - foreach (var (value, view) in array) + for (var i = 0; i < array.Length; i++) { - filter.InvokeOnAttach(value, view); + var (value, view) = array[i]; + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } @@ -178,7 +194,6 @@ public void ResetFilter(Action? resetAction) public void Dispose() { - } public void Sort(IComparer comparer) diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index 327de38..25efb63 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -55,7 +55,7 @@ public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged remove { parent.RoutingCollectionChanged -= value; } } - public void AttachFilter(ISynchronizedViewFilter filter) => parent.AttachFilter(filter); + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) => parent.AttachFilter(filter, invokeAddEventForCurrentElements); public void ResetFilter(Action? resetAction) => parent.ResetFilter(resetAction); public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() => this; public void Dispose() diff --git a/src/ObservableCollections/Internal/SortedView.cs b/src/ObservableCollections/Internal/SortedView.cs index 0411985..62a8a00 100644 --- a/src/ObservableCollections/Internal/SortedView.cs +++ b/src/ObservableCollections/Internal/SortedView.cs @@ -51,14 +51,21 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; foreach (var (_, (value, view)) in dict) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, -1)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } diff --git a/src/ObservableCollections/Internal/SortedViewViewComparer.cs b/src/ObservableCollections/Internal/SortedViewViewComparer.cs index 383c2eb..3731888 100644 --- a/src/ObservableCollections/Internal/SortedViewViewComparer.cs +++ b/src/ObservableCollections/Internal/SortedViewViewComparer.cs @@ -55,14 +55,21 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; foreach (var (_, (value, view)) in dict) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, -1)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index fff8918..0dd794a 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -55,14 +55,23 @@ public void Dispose() this.source.CollectionChanged -= SourceCollectionChanged; } - public void AttachFilter(ISynchronizedViewFilter, TView> filter) + public void AttachFilter(ISynchronizedViewFilter, TView> filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; foreach (var v in dict) { - filter.InvokeOnAttach(new KeyValuePair(v.Key, v.Value.Item1), v.Value.Item2); + var value = new KeyValuePair(v.Key, v.Value.Item1); + var view = v.Value.Item2; + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs>.Add(value, -1)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } @@ -118,44 +127,44 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs(e.NewItem.Key, e.NewItem.Value), v, e); - } + { + var v = selector(e.NewItem); + dict.Add(e.NewItem.Key, (e.NewItem.Value, v)); + filter.InvokeOnAdd(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v, e); + } break; case NotifyCollectionChangedAction.Remove: + { + if (dict.Remove(e.OldItem.Key, out var v)) { - if (dict.Remove(e.OldItem.Key, out var v)) - { - filter.InvokeOnRemove((new KeyValuePair(e.OldItem.Key, v.Item1), v.Item2), e); - } + filter.InvokeOnRemove((new KeyValuePair(e.OldItem.Key, v.Item1), v.Item2), e); } + } break; case NotifyCollectionChangedAction.Replace: + { + if (dict.Remove(e.OldItem.Key, out var oldView)) { - if (dict.Remove(e.OldItem.Key, out var oldView)) - { - filter.InvokeOnRemove((new KeyValuePair(e.OldItem.Key, oldView.Item1), oldView.Item2), e); - } - - var v = selector(e.NewItem); - dict[e.NewItem.Key] = (e.NewItem.Value, v); - filter.InvokeOnAdd(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v, e); + filter.InvokeOnRemove((new KeyValuePair(e.OldItem.Key, oldView.Item1), oldView.Item2), e); } + + var v = selector(e.NewItem); + dict[e.NewItem.Key] = (e.NewItem.Value, v); + filter.InvokeOnAdd(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v, e); + } break; case NotifyCollectionChangedAction.Reset: + { + if (!filter.IsNullFilter()) { - if (!filter.IsNullFilter()) + foreach (var item in dict) { - foreach (var item in dict) - { - filter.InvokeOnRemove((new KeyValuePair(item.Key, item.Value.Item1), item.Value.Item2), e); - } + filter.InvokeOnRemove((new KeyValuePair(item.Key, item.Value.Item1), item.Value.Item2), e); } - - dict.Clear(); } + + dict.Clear(); + } break; case NotifyCollectionChangedAction.Move: // ObservableDictionary have no Move operation. default: @@ -168,4 +177,4 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; foreach (var (_, (value, view)) in dict) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd((value, view), NotifyCollectionChangedEventArgs.Add(value, -1)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index aae921f..ed95711 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -53,14 +53,23 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; - foreach (var (value, view) in list) + for (var i = 0; i < list.Count; i++) { - filter.InvokeOnAttach(value, view); + var (value, view) = list[i]; + if (invokeAddEventForCurrentElements) + { + var eventArgs = NotifyCollectionChangedEventArgs.Add(value, i); + filter.InvokeOnAdd(value, view, eventArgs); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } @@ -193,24 +202,24 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) break; case NotifyCollectionChangedAction.Replace: // ObservableList does not support replace range - { - var v = (e.NewItem, selector(e.NewItem)); + { + var v = (e.NewItem, selector(e.NewItem)); - var oldItem = list[e.NewStartingIndex]; - list[e.NewStartingIndex] = v; + var oldItem = list[e.NewStartingIndex]; + list[e.NewStartingIndex] = v; - filter.InvokeOnRemove(oldItem, e); - filter.InvokeOnAdd(v, e); - break; - } + filter.InvokeOnRemove(oldItem, e); + filter.InvokeOnAdd(v, e); + break; + } case NotifyCollectionChangedAction.Move: - { - var removeItem = list[e.OldStartingIndex]; - list.RemoveAt(e.OldStartingIndex); - list.Insert(e.NewStartingIndex, removeItem); + { + var removeItem = list[e.OldStartingIndex]; + list.RemoveAt(e.OldStartingIndex); + list.Insert(e.NewStartingIndex, removeItem); - filter.InvokeOnMove(removeItem, e); - } + filter.InvokeOnMove(removeItem, e); + } break; case NotifyCollectionChangedAction.Reset: if (!filter.IsNullFilter()) diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index 76b245a..5f551b6 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -53,14 +53,23 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; + var i = 0; foreach (var (value, view) in queue) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } + i++; } } } diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index 987bf55..3562549 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -54,14 +54,22 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; - foreach (var (value, view) in ringBuffer) + for (var i = 0; i < ringBuffer.Count; i++) { - filter.InvokeOnAttach(value, view); + var (value, view) = ringBuffer[i]; + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } } } } diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index a4aaf79..5175732 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -53,14 +53,23 @@ public int Count } } - public void AttachFilter(ISynchronizedViewFilter filter) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { this.filter = filter; + var i = 0; foreach (var (value, view) in stack) { - filter.InvokeOnAttach(value, view); + if (invokeAddEventForCurrentElements) + { + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, i)); + } + else + { + filter.InvokeOnAttach(value, view); + } + i++; } } } diff --git a/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs b/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs index 69329c5..9eb733c 100644 --- a/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs +++ b/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs @@ -154,5 +154,36 @@ public void FilterTest() .OrderBy(x => x.Value) .Should().Equal((ChangedKind.Remove, -1090), (ChangedKind.Remove, -100), (ChangedKind.Remove, -53), (ChangedKind.Remove, -40), (ChangedKind.Remove, -34)); } + + [Fact] + public void FilterAndInvokeAddEvent() + { + var dict = new ObservableDictionary(); + var view1 = dict.CreateView(x => new ViewContainer(x.Value)); + var filter1 = new TestFilter2((x, v) => x.Value % 2 == 0); + + dict.Add(10, -12); // 0 + dict.Add(50, -53); // 1 + dict.Add(30, -34); // 2 + dict.Add(20, -25); // 3 + dict.Add(40, -40); // 4 + + view1.AttachFilter(filter1, true); + + filter1.CalledOnCollectionChanged.Count.Should().Be(5); + filter1.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[0].value.Key.Should().Be(10); + filter1.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[1].value.Key.Should().Be(50); + filter1.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[2].value.Key.Should().Be(30); + filter1.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[3].value.Key.Should().Be(20); + filter1.CalledOnCollectionChanged[4].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[4].value.Key.Should().Be(40); + + filter1.CalledWhenTrue.Count.Should().Be(3); + filter1.CalledWhenFalse.Count.Should().Be(2); + } } } diff --git a/tests/ObservableCollections.Tests/ObservableHashSetTest.cs b/tests/ObservableCollections.Tests/ObservableHashSetTest.cs index 0b256a8..fcddbb8 100644 --- a/tests/ObservableCollections.Tests/ObservableHashSetTest.cs +++ b/tests/ObservableCollections.Tests/ObservableHashSetTest.cs @@ -76,5 +76,35 @@ public void Filter() set.RemoveRange(new[] { 50, 30 }); filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Remove, 10), (ChangedKind.Remove, 50), (ChangedKind.Remove, 30)); } + + [Fact] + public void FilterAndInvokeAddEvent() + { + var set = new ObservableHashSet(); + var view = set.CreateView(x => new ViewContainer(x)); + var filter = new TestFilter((x, v) => x % 3 == 0); + + set.Add(10); + set.Add(50); + set.Add(30); + set.Add(20); + set.Add(40); + + view.AttachFilter(filter, true); + filter.CalledOnCollectionChanged.Count.Should().Be(5); + filter.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[0].value.Should().Be(10); + filter.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[1].value.Should().Be(50); + filter.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[2].value.Should().Be(30); + filter.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[3].value.Should().Be(20); + filter.CalledOnCollectionChanged[4].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[4].value.Should().Be(40); + + filter.CalledWhenTrue.Count.Should().Be(1); + filter.CalledWhenFalse.Count.Should().Be(4); + } } } diff --git a/tests/ObservableCollections.Tests/ObservableListTest.cs b/tests/ObservableCollections.Tests/ObservableListTest.cs index e4acbac..edf28b9 100644 --- a/tests/ObservableCollections.Tests/ObservableListTest.cs +++ b/tests/ObservableCollections.Tests/ObservableListTest.cs @@ -218,5 +218,28 @@ public void FilterTest() filter2.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Remove, 21), (ChangedKind.Remove, 30), (ChangedKind.Remove, 44), (ChangedKind.Remove, 45), (ChangedKind.Remove, 66), (ChangedKind.Remove, 90), (ChangedKind.Remove, 100), (ChangedKind.Remove, 101), (ChangedKind.Remove, 9999)); filter3.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Remove, 21), (ChangedKind.Remove, 30), (ChangedKind.Remove, 44), (ChangedKind.Remove, 45), (ChangedKind.Remove, 66), (ChangedKind.Remove, 90), (ChangedKind.Remove, 100), (ChangedKind.Remove, 101), (ChangedKind.Remove, 9999)); } + + [Fact] + public void FilterAndInvokeAddEvent() + { + var list = new ObservableList(); + var view1 = list.CreateView(x => new ViewContainer(x)); + list.AddRange(new[] { 10, 21, 30, 44 }); + + var filter1 = new TestFilter((x, v) => x % 2 == 0); + view1.AttachFilter(filter1, true); + + filter1.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[0].value.Should().Be(10); + filter1.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[1].value.Should().Be(21); + filter1.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[2].value.Should().Be(30); + filter1.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add); + filter1.CalledOnCollectionChanged[3].value.Should().Be(44); + + filter1.CalledWhenTrue.Count.Should().Be(3); + filter1.CalledWhenFalse.Count.Should().Be(1); + } } -} +} \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/ObservableQueueTest.cs b/tests/ObservableCollections.Tests/ObservableQueueTest.cs index 6cd07cd..bfdb08f 100644 --- a/tests/ObservableCollections.Tests/ObservableQueueTest.cs +++ b/tests/ObservableCollections.Tests/ObservableQueueTest.cs @@ -80,5 +80,36 @@ public void Filter() queue.DequeueRange(2); filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Remove, 10), (ChangedKind.Remove, 50), (ChangedKind.Remove, 30)); } + + [Fact] + public void FilterAndInvokeAddEvent() + { + var queue = new ObservableQueue(); + var view = queue.CreateView(x => new ViewContainer(x)); + var filter = new TestFilter((x, v) => x % 3 == 0); + + queue.Enqueue(10); + queue.Enqueue(50); + queue.Enqueue(30); + queue.Enqueue(20); + queue.Enqueue(40); + + view.AttachFilter(filter, true); + + filter.CalledOnCollectionChanged.Count.Should().Be(5); + filter.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[0].value.Should().Be(10); + filter.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[1].value.Should().Be(50); + filter.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[2].value.Should().Be(30); + filter.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[3].value.Should().Be(20); + filter.CalledOnCollectionChanged[4].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[4].value.Should().Be(40); + + filter.CalledWhenTrue.Count.Should().Be(1); + filter.CalledWhenFalse.Count.Should().Be(4); + } } } diff --git a/tests/ObservableCollections.Tests/ObservableStackTest.cs b/tests/ObservableCollections.Tests/ObservableStackTest.cs index 0da4131..02a2b9c 100644 --- a/tests/ObservableCollections.Tests/ObservableStackTest.cs +++ b/tests/ObservableCollections.Tests/ObservableStackTest.cs @@ -80,5 +80,36 @@ public void Filter() stack.PopRange(2); filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Remove, 98), (ChangedKind.Remove, 33), (ChangedKind.Remove, 40)); } + + [Fact] + public void FilterAndInvokeAddEvent() + { + var stack = new ObservableStack(); + var view = stack.CreateView(x => new ViewContainer(x)); + var filter = new TestFilter((x, v) => x % 3 == 0); + + stack.Push(10); + stack.Push(50); + stack.Push(30); + stack.Push(20); + stack.Push(40); + + view.AttachFilter(filter, true); + filter.CalledOnCollectionChanged.Count.Should().Be(5); + filter.CalledOnCollectionChanged[4].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[4].value.Should().Be(10); + filter.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[3].value.Should().Be(50); + filter.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[2].value.Should().Be(30); + filter.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[1].value.Should().Be(20); + filter.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[0].value.Should().Be(40); + + filter.CalledWhenTrue.Count.Should().Be(1); + filter.CalledWhenFalse.Count.Should().Be(4); + } + } }