Fix (NotifyCollecitonChanged)SynchronizedViewList does not sync correcty when view is attached filter

This commit is contained in:
neuecc 2024-10-02 18:28:05 +09:00
parent b73a30c366
commit 0ce9b3a10e
12 changed files with 212 additions and 153 deletions

View File

@ -466,6 +466,11 @@ This is the interface for View:
```csharp
public delegate void NotifyViewChangedEventHandler<T, TView>(in SynchronizedViewChangedEventArgs<T, TView> e);
public enum RejectedViewChangedAction
{
Add, Remove, Move
}
public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisposable
{
object SyncRoot { get; }
@ -475,6 +480,7 @@ public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisp
int UnfilteredCount { get; }
event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; // int index, int oldIndex(when RejectedViewChangedAction is Move)
event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
void AttachFilter(ISynchronizedViewFilter<T> filter);

View File

@ -8,68 +8,80 @@ using System.Collections.Generic;
var buffer = new ObservableList<int>(5);
// Queue <-> List Synchronization
var queue = new ObservableQueue<int>();
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<int, string> 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<int>
{
public bool IsMatch(int value)
{
return value % 2 == 0;
}
public void OnCollectionChanged(in SynchronizedViewChangedEventArgs<int, ViewModel> 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);
}
}
}
//var buffer = new ObservableFixedSizeRingBuffer<int>(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);
//}

View File

@ -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<T> : IEnumerable<T>
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<T> : IEnumerable<T>
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<T> : IEnumerable<T>
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<T>.Default.Equals(x.Value, searchValue));

View File

@ -25,6 +25,11 @@ namespace ObservableCollections
{
}
public enum RejectedViewChangedAction
{
Add, Remove, Move
}
public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisposable
{
object SyncRoot { get; }
@ -34,6 +39,7 @@ namespace ObservableCollections
int UnfilteredCount { get; }
event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
void AttachFilter(ISynchronizedViewFilter<T> filter);

View File

@ -39,6 +39,7 @@ namespace ObservableCollections
public object SyncRoot { get; }
public event NotifyViewChangedEventHandler<KeyValuePair<TKey, TValue>, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
public ISynchronizedViewFilter<KeyValuePair<TKey, TValue>> 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;

View File

@ -30,6 +30,7 @@ namespace ObservableCollections
ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
public event Action<NotifyCollectionChangedAction>? 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);
}
}
}

View File

@ -32,6 +32,7 @@ namespace ObservableCollections
ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
public event Action<NotifyCollectionChangedAction>? 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:

View File

@ -25,6 +25,7 @@ namespace ObservableCollections
ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
public event Action<NotifyCollectionChangedAction>? 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;

View File

@ -31,6 +31,7 @@ namespace ObservableCollections
ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
public event Action<NotifyCollectionChangedAction>? 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);
}
}
}

View File

@ -24,6 +24,7 @@ namespace ObservableCollections
ISynchronizedViewFilter<T> filter;
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
public event Action<NotifyCollectionChangedAction>? 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;

View File

@ -94,108 +94,108 @@ namespace ObservableCollections
return filter == SynchronizedViewFilter<T>.Null;
}
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, (T value, TView view) value, int index)
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? 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<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, int index)
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int index)
{
var isMatch = collection.Filter.IsMatch(value);
if (isMatch)
{
filteredCount++;
if (ev != null)
{
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
}
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
}
}
internal static void InvokeOnAddRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
{
if (ev != null)
else
{
if (isMatchAll)
ev2?.Invoke(RejectedViewChangedAction.Add, index, -1);
}
}
internal static void InvokeOnAddRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
{
if (isMatchAll)
{
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, isSingleItem: false, newValues: values, newViews: views, newStartingIndex: index));
}
else
{
for (var i = 0; i < matches.Length; i++)
{
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(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<T, TView>(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: startingIndex++));
}
var item = (values[i], views[i]);
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: index));
}
else
{
ev2?.Invoke(RejectedViewChangedAction.Add, index, -1);
}
index++;
}
}
}
internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, (T value, TView view) value, int oldIndex)
internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? 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<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, int oldIndex)
internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int oldIndex)
{
var isMatch = collection.Filter.IsMatch(value);
if (isMatch)
{
filteredCount--;
if (ev != null)
{
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
}
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
}
else
{
ev2?.Invoke(RejectedViewChangedAction.Remove, oldIndex, -1);
}
}
// only use for ObservableList
internal static void InvokeOnRemoveRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
internal static void InvokeOnRemoveRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
{
if (ev != null)
if (isMatchAll)
{
if (isMatchAll)
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, isSingleItem: false, oldValues: values, oldViews: views, oldStartingIndex: index));
}
else
{
for (var i = 0; i < matches.Length; i++)
{
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(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<T, TView>(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<T, TView>(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<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, (T value, TView view) value, int index, int oldIndex)
internal static void InvokeOnMove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? 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<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, int index, int oldIndex)
internal static void InvokeOnMove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? 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<T, TView>(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex));
}
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(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<T, TView>(NotifyCollectionChangedAction.Replace, true, newItem: (value, view), oldItem: (oldValue, oldView), newStartingIndex: index, oldStartingIndex: oldIndex >= 0 ? oldIndex : index));
}
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(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<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
}
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
}
else if (newMatched)
{
// only-new is add
filteredCount++;
if (ev != null)
{
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
}
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
}
}

View File

@ -23,6 +23,7 @@ internal class FiltableSynchronizedViewList<T, TView> : ISynchronizedViewList<TV
{
listView = new AlternateIndexList<TView>(IterateFilteredIndexedViewsOfParent());
parent.ViewChanged += Parent_ViewChanged;
parent.RejectedViewChanged += Parent_RejectedViewChanged;
}
}
@ -153,6 +154,27 @@ internal class FiltableSynchronizedViewList<T, TView> : ISynchronizedViewList<TV
}
}
private void Parent_RejectedViewChanged(RejectedViewChangedAction arg1, int index, int oldIndex)
{
lock (gate)
{
switch (arg1)
{
case RejectedViewChangedAction.Add:
listView.UpdateAlternateIndex(index, 1);
break;
case RejectedViewChangedAction.Remove:
listView.UpdateAlternateIndex(index, -1);
break;
case RejectedViewChangedAction.Move:
listView.TryReplaceAlternateIndex(oldIndex, index);
break;
default:
break;
}
}
}
protected virtual void OnCollectionChanged(in SynchronizedViewChangedEventArgs<T, TView> args)
{
}
@ -198,6 +220,7 @@ internal class FiltableSynchronizedViewList<T, TView> : ISynchronizedViewList<TV
public void Dispose()
{
parent.ViewChanged -= Parent_ViewChanged;
parent.RejectedViewChanged -= Parent_RejectedViewChanged;
}
}