From 0ce9b3a10e41a5fae34d1ca261fac695e9744074 Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 2 Oct 2024 18:28:05 +0900 Subject: [PATCH] Fix (NotifyCollecitonChanged)SynchronizedViewList does not sync correcty when view is attached filter --- README.md | 6 + sandbox/ConsoleApp/Program.cs | 114 ++++++++------- .../AlternateIndexList.cs | 22 ++- .../IObservableCollection.cs | 6 + .../ObservableDictionary.Views.cs | 5 +- .../ObservableHashSet.Views.cs | 9 +- .../ObservableList.Views.cs | 11 +- .../ObservableQueue.Views.cs | 9 +- .../ObservableRingBuffer.Views.cs | 17 +-- .../ObservableStack.Views.cs | 9 +- .../SynchronizedViewChangedEventArgs.cs | 134 ++++++++---------- .../SynchronizedViewList.cs | 23 +++ 12 files changed, 212 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 8170ac4..6669244 100644 --- a/README.md +++ b/README.md @@ -466,6 +466,11 @@ This is the interface for View: ```csharp public delegate void NotifyViewChangedEventHandler(in SynchronizedViewChangedEventArgs e); +public enum RejectedViewChangedAction +{ + Add, Remove, Move +} + public interface ISynchronizedView : IReadOnlyCollection, IDisposable { object SyncRoot { get; } @@ -475,6 +480,7 @@ public interface ISynchronizedView : IReadOnlyCollection, IDisp int UnfilteredCount { get; } event NotifyViewChangedEventHandler? ViewChanged; + event Action? RejectedViewChanged; // int index, int oldIndex(when RejectedViewChangedAction is Move) event Action? CollectionStateChanged; void AttachFilter(ISynchronizedViewFilter filter); diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index f547f51..1c63b22 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -8,68 +8,80 @@ using System.Collections.Generic; +var buffer = new ObservableList(5); -// Queue <-> List Synchronization -var queue = new ObservableQueue(); +var view = buffer.CreateView(value => value); +view.AttachFilter(value => value % 2 == 1); // when filtered, mismatch...! -queue.Enqueue(1); -queue.Enqueue(10); -queue.Enqueue(100); -queue.Enqueue(1000); -queue.Enqueue(10000); +//{ +// INotifyCollectionChangedSynchronizedViewList created from ISynchronizedView with a filter. +var collection = view.ToNotifyCollectionChanged(); -using var view = queue.CreateView(x => x.ToString() + "$"); +// Not disposed here. +//} -using var viewList = view.ToViewList(); +buffer.Insert(0, 1); +buffer.Insert(0, 1); +buffer.Insert(0, 2); +buffer.Insert(0, 3); +buffer.Insert(0, 5); +buffer.RemoveAt(buffer.Count - 1); -Console.WriteLine(viewList[2]); // 100$ +buffer.Insert(0, 8); +buffer.Insert(0, 13); -view.ViewChanged += View_ViewChanged; +buffer.Move(1, 5); -void View_ViewChanged(in SynchronizedViewChangedEventArgs eventArgs) +foreach (var item in view) { - if (eventArgs.Action == NotifyCollectionChangedAction.Add) - { - // eventArgs.OldItem.View. - } + Console.WriteLine(item); +} +Console.WriteLine("---"); - throw new NotImplementedException(); +foreach (var item in collection) +{ + + Console.WriteLine(item); } -class ViewModel -{ - public int Id { get; set; } - public string Value { get; set; } = default!; -} -class HogeFilter : ISynchronizedViewFilter -{ - public bool IsMatch(int value) - { - return value % 2 == 0; - } - public void OnCollectionChanged(in SynchronizedViewChangedEventArgs eventArgs) - { - switch (eventArgs.Action) - { - case NotifyCollectionChangedAction.Add: - eventArgs.NewItem.View.Value += " Add"; - break; - case NotifyCollectionChangedAction.Remove: - eventArgs.OldItem.View.Value += " Remove"; - break; - case NotifyCollectionChangedAction.Move: - eventArgs.NewItem.View.Value += $" Move {eventArgs.OldStartingIndex} {eventArgs.NewStartingIndex}"; - break; - case NotifyCollectionChangedAction.Replace: - eventArgs.NewItem.View.Value += $" Replace {eventArgs.NewStartingIndex}"; - break; - case NotifyCollectionChangedAction.Reset: - break; - default: - throw new ArgumentOutOfRangeException(nameof(eventArgs.Action), eventArgs.Action, null); - } - } -} \ No newline at end of file +//var buffer = new ObservableFixedSizeRingBuffer(5); + +//var view = buffer.CreateView(value => value); +//view.AttachFilter(value => value % 2 == 1); // when filtered, mismatch...! + +////{ +//// INotifyCollectionChangedSynchronizedViewList created from ISynchronizedView with a filter. +//var collection = view.ToNotifyCollectionChanged(); + +//// Not disposed here. +////} + +//buffer.AddFirst(1); +//buffer.AddFirst(1); +//buffer.AddFirst(2); +//buffer.AddFirst(3); +//buffer.AddFirst(5); +//buffer.AddFirst(8); // Argument out of range +//buffer.AddFirst(13); + +//foreach (var item in collection) +//{ +// Console.WriteLine(item); +//} + +//Console.WriteLine("---"); + +//foreach (var item in view) +//{ +// Console.WriteLine(item); +//} + +//Console.WriteLine("---"); + +//foreach (var item in buffer) +//{ +// Console.WriteLine(item); +//} \ No newline at end of file diff --git a/src/ObservableCollections/AlternateIndexList.cs b/src/ObservableCollections/AlternateIndexList.cs index 19d3110..23ea1be 100644 --- a/src/ObservableCollections/AlternateIndexList.cs +++ b/src/ObservableCollections/AlternateIndexList.cs @@ -3,7 +3,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; @@ -24,7 +23,7 @@ public class AlternateIndexList : IEnumerable this.list = values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)).ToList(); } - void UpdateAlternateIndex(int startIndex, int incr) + public void UpdateAlternateIndex(int startIndex, int incr) { var span = CollectionsMarshal.AsSpan(list); for (int i = startIndex; i < span.Length; i++) @@ -80,11 +79,15 @@ public class AlternateIndexList : IEnumerable public int RemoveAt(int alternateIndex) { var index = list.BinarySearch(alternateIndex); - if (index != -1) + if (index >= 0) { list.RemoveAt(index); UpdateAlternateIndex(index, -1); } + else + { + throw new InvalidOperationException("Index was not found. AlternateIndex:" + alternateIndex); + } return index; } @@ -124,6 +127,19 @@ public class AlternateIndexList : IEnumerable return true; } + public bool TryReplaceAlternateIndex(int getAlternateIndex, int setAlternateIndex) + { + var index = list.BinarySearch(getAlternateIndex); + if (index < 0) + { + return false; + } + + var span = CollectionsMarshal.AsSpan(list); + span[index].AlternateIndex = setAlternateIndex; + return true; + } + public bool TryReplaceByValue(T searchValue, T replaceValue, out int replacedIndex) { replacedIndex = list.FindIndex(x => EqualityComparer.Default.Equals(x.Value, searchValue)); diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 5ef0f92..680c020 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -25,6 +25,11 @@ namespace ObservableCollections { } + public enum RejectedViewChangedAction + { + Add, Remove, Move + } + public interface ISynchronizedView : IReadOnlyCollection, IDisposable { object SyncRoot { get; } @@ -34,6 +39,7 @@ namespace ObservableCollections int UnfilteredCount { get; } event NotifyViewChangedEventHandler? ViewChanged; + event Action? RejectedViewChanged; event Action? CollectionStateChanged; void AttachFilter(ISynchronizedViewFilter filter); diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 4f0f77f..666aa48 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -39,6 +39,7 @@ namespace ObservableCollections public object SyncRoot { get; } public event NotifyViewChangedEventHandler, TView>? ViewChanged; + public event Action? RejectedViewChanged; public event Action? CollectionStateChanged; public ISynchronizedViewFilter> Filter @@ -184,14 +185,14 @@ namespace ObservableCollections { var v = selector(e.NewItem); dict.Add(e.NewItem.Key, (e.NewItem.Value, v)); - this.InvokeOnAdd(ref filteredCount, ViewChanged, e.NewItem, v, -1); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, e.NewItem, v, -1); } break; case NotifyCollectionChangedAction.Remove: { if (dict.Remove(e.OldItem.Key, out var v)) { - this.InvokeOnRemove(ref filteredCount, ViewChanged, e.OldItem, v.Item2, -1); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, e.OldItem, v.Item2, -1); } } break; diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index 8388138..d8adf18 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -30,6 +30,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; public event NotifyViewChangedEventHandler? ViewChanged; + public event Action? RejectedViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -181,7 +182,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); dict.Add(e.NewItem, v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, -1); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, -1); } else { @@ -190,7 +191,7 @@ namespace ObservableCollections { var v = (item, selector(item)); dict.Add(item, v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, i++); } } break; @@ -199,7 +200,7 @@ namespace ObservableCollections { if (dict.Remove(e.OldItem, out var value)) { - this.InvokeOnRemove(ref filteredCount, ViewChanged, value, -1); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, value, -1); } } else @@ -208,7 +209,7 @@ namespace ObservableCollections { if (dict.Remove(item, out var value)) { - this.InvokeOnRemove(ref filteredCount, ViewChanged, value, -1); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, value, -1); } } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index fad0903..5b84818 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -32,6 +32,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; public event NotifyViewChangedEventHandler? ViewChanged; + public event Action? RejectedViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -185,7 +186,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); list.Insert(e.NewStartingIndex, v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, e.NewStartingIndex); } else { @@ -214,7 +215,7 @@ namespace ObservableCollections } list.InsertRange(e.NewStartingIndex, valueViews.Span); - this.InvokeOnAddRange(ViewChanged, e.NewItems, views.Span, isMatchAll, matches.Span, e.NewStartingIndex); + this.InvokeOnAddRange(ViewChanged, RejectedViewChanged, e.NewItems, views.Span, isMatchAll, matches.Span, e.NewStartingIndex); } break; case NotifyCollectionChangedAction.Remove: @@ -222,7 +223,7 @@ namespace ObservableCollections { var v = list[e.OldStartingIndex]; list.RemoveAt(e.OldStartingIndex); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, e.OldStartingIndex); } else { @@ -251,7 +252,7 @@ namespace ObservableCollections } list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); - this.InvokeOnRemoveRange(ViewChanged, values.Span, views.Span, isMatchAll, matches.Span, e.OldStartingIndex); + this.InvokeOnRemoveRange(ViewChanged, RejectedViewChanged, values.Span, views.Span, isMatchAll, matches.Span, e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Replace: @@ -269,7 +270,7 @@ namespace ObservableCollections list.RemoveAt(e.OldStartingIndex); list.Insert(e.NewStartingIndex, removeItem); - this.InvokeOnMove(ref filteredCount, ViewChanged, removeItem, e.NewStartingIndex, e.OldStartingIndex); + this.InvokeOnMove(ref filteredCount, ViewChanged, RejectedViewChanged, removeItem, e.NewStartingIndex, e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Reset: diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index c4aa63c..812dee8 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -25,6 +25,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; public event NotifyViewChangedEventHandler? ViewChanged; + public event Action? RejectedViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -182,7 +183,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); queue.Enqueue(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, e.NewStartingIndex); } else { @@ -191,7 +192,7 @@ namespace ObservableCollections { var v = (item, selector(item)); queue.Enqueue(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, i++); } } break; @@ -200,7 +201,7 @@ namespace ObservableCollections if (e.IsSingleItem) { var v = queue.Dequeue(); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v.Item1, v.Item2, 0); } else { @@ -208,7 +209,7 @@ namespace ObservableCollections for (int i = 0; i < len; i++) { var v = queue.Dequeue(); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v.Item1, v.Item2, 0); } } break; diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index c539d1b..8b951dd 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -31,6 +31,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; public event NotifyViewChangedEventHandler? ViewChanged; + public event Action? RejectedViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -196,7 +197,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); ringBuffer.AddFirst(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0); } else { @@ -204,7 +205,7 @@ namespace ObservableCollections { var v = (item, selector(item)); ringBuffer.AddFirst(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0); } } } @@ -215,7 +216,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); ringBuffer.AddLast(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, ringBuffer.Count - 1); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, ringBuffer.Count - 1); } else { @@ -223,7 +224,7 @@ namespace ObservableCollections { var v = (item, selector(item)); ringBuffer.AddLast(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, ringBuffer.Count - 1); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, ringBuffer.Count - 1); } } } @@ -236,14 +237,14 @@ namespace ObservableCollections if (e.IsSingleItem) { var v = ringBuffer.RemoveFirst(); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0); } else { for (int i = 0; i < e.OldItems.Length; i++) { var v = ringBuffer.RemoveFirst(); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0); } } } @@ -254,7 +255,7 @@ namespace ObservableCollections { var index = ringBuffer.Count - 1; var v = ringBuffer.RemoveLast(); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v, index); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, index); } else { @@ -262,7 +263,7 @@ namespace ObservableCollections { var index = ringBuffer.Count - 1; var v = ringBuffer.RemoveLast(); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v, index); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, index); } } } diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index 81250a8..f0feeab 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -24,6 +24,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; public event NotifyViewChangedEventHandler? ViewChanged; + public event Action? RejectedViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -187,7 +188,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); stack.Push(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0); } else { @@ -195,7 +196,7 @@ namespace ObservableCollections { var v = (item, selector(item)); stack.Push(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0); + this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0); } } break; @@ -204,7 +205,7 @@ namespace ObservableCollections if (e.IsSingleItem) { var v = stack.Pop(); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v.Item1, v.Item2, 0); } else { @@ -212,7 +213,7 @@ namespace ObservableCollections for (int i = 0; i < len; i++) { var v = stack.Pop(); - this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v.Item1, v.Item2, 0); } } break; diff --git a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs index 220ad83..42aae20 100644 --- a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs +++ b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs @@ -94,108 +94,108 @@ namespace ObservableCollections return filter == SynchronizedViewFilter.Null; } - internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int index) + internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, Action? ev2, (T value, TView view) value, int index) { - InvokeOnAdd(collection, ref filteredCount, ev, value.value, value.view, index); + InvokeOnAdd(collection, ref filteredCount, ev, ev2, value.value, value.view, index); } - internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int index) + internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, Action? ev2, T value, TView view, int index) { var isMatch = collection.Filter.IsMatch(value); if (isMatch) { filteredCount++; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index)); - } + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index)); } - } - - internal static void InvokeOnAddRange(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, bool isMatchAll, ReadOnlySpan matches, int index) - { - if (ev != null) + else { - if (isMatchAll) + ev2?.Invoke(RejectedViewChangedAction.Add, index, -1); + } + } + + internal static void InvokeOnAddRange(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, Action? ev2, ReadOnlySpan values, ReadOnlySpan views, bool isMatchAll, ReadOnlySpan matches, int index) + { + if (isMatchAll) + { + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, isSingleItem: false, newValues: values, newViews: views, newStartingIndex: index)); + } + else + { + for (var i = 0; i < matches.Length; i++) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, isSingleItem: false, newValues: values, newViews: views, newStartingIndex: index)); - } - else - { - var startingIndex = index; - for (var i = 0; i < matches.Length; i++) + if (matches[i]) { - if (matches[i]) - { - var item = (values[i], views[i]); - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: startingIndex++)); - } + var item = (values[i], views[i]); + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: index)); } + else + { + ev2?.Invoke(RejectedViewChangedAction.Add, index, -1); + } + index++; } } } - internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int oldIndex) + internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, Action? ev2, (T value, TView view) value, int oldIndex) { - InvokeOnRemove(collection, ref filteredCount, ev, value.value, value.view, oldIndex); + InvokeOnRemove(collection, ref filteredCount, ev, ev2, value.value, value.view, oldIndex); } - internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int oldIndex) + internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, Action? ev2, T value, TView view, int oldIndex) { var isMatch = collection.Filter.IsMatch(value); if (isMatch) { filteredCount--; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex)); - } + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex)); + } + else + { + ev2?.Invoke(RejectedViewChangedAction.Remove, oldIndex, -1); } } // only use for ObservableList - internal static void InvokeOnRemoveRange(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, bool isMatchAll, ReadOnlySpan matches, int index) + internal static void InvokeOnRemoveRange(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, Action? ev2, ReadOnlySpan values, ReadOnlySpan views, bool isMatchAll, ReadOnlySpan matches, int index) { - if (ev != null) + if (isMatchAll) { - if (isMatchAll) + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isSingleItem: false, oldValues: values, oldViews: views, oldStartingIndex: index)); + } + else + { + for (var i = 0; i < matches.Length; i++) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isSingleItem: false, oldValues: values, oldViews: views, oldStartingIndex: index)); - } - else - { - var startingIndex = index; - for (var i = 0; i < matches.Length; i++) + if (matches[i]) { - if (matches[i]) - { - var item = (values[i], views[i]); - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isSingleItem: true, oldItem: item, oldStartingIndex: index)); //remove for list, always same index - } - else - { - index++; // not matched, skip index - } + var item = (values[i], views[i]); + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isSingleItem: true, oldItem: item, oldStartingIndex: index)); //remove for list, always same index + } + else + { + ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1); } } } } - internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int index, int oldIndex) + internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, Action? ev2, (T value, TView view) value, int index, int oldIndex) { - InvokeOnMove(collection, ref filteredCount, ev, value.value, value.view, index, oldIndex); + InvokeOnMove(collection, ref filteredCount, ev, ev2, value.value, value.view, index, oldIndex); } - internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int index, int oldIndex) + internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, Action? ev2, T value, TView view, int index, int oldIndex) { - if (ev != null) + // move does not changes filtered-count + var isMatch = collection.Filter.IsMatch(value); + if (isMatch) { - // move does not changes filtered-count - var isMatch = collection.Filter.IsMatch(value); - if (isMatch) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex)); - } + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex)); + } + else + { + ev2?.Invoke(RejectedViewChangedAction.Move, index, oldIndex); } } @@ -212,29 +212,19 @@ namespace ObservableCollections if (bothMatched) { - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, true, newItem: (value, view), oldItem: (oldValue, oldView), newStartingIndex: index, oldStartingIndex: oldIndex >= 0 ? oldIndex : index)); - } + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, true, newItem: (value, view), oldItem: (oldValue, oldView), newStartingIndex: index, oldStartingIndex: oldIndex >= 0 ? oldIndex : index)); } else if (oldMatched) { // only-old is remove filteredCount--; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex)); - } - + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex)); } else if (newMatched) { // only-new is add filteredCount++; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index)); - } + ev?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index)); } } diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index eb16d64..b9f7c4c 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -23,6 +23,7 @@ internal class FiltableSynchronizedViewList : ISynchronizedViewList(IterateFilteredIndexedViewsOfParent()); parent.ViewChanged += Parent_ViewChanged; + parent.RejectedViewChanged += Parent_RejectedViewChanged; } } @@ -153,6 +154,27 @@ internal class FiltableSynchronizedViewList : ISynchronizedViewList args) { } @@ -198,6 +220,7 @@ internal class FiltableSynchronizedViewList : ISynchronizedViewList