Merge remote-tracking branch 'origin/master'

This commit is contained in:
neuecc 2024-10-02 18:29:16 +09:00
commit b46163e045
11 changed files with 219 additions and 77 deletions

View File

@ -58,7 +58,7 @@ Observable<CollectionResetEvent<T>> IObservableCollection<T>.ObserveReset()
Observable<CollectionResetEvent<T>> IObservableCollection<T>.ObserveReset() Observable<CollectionResetEvent<T>> IObservableCollection<T>.ObserveReset()
Observable<Unit> IObservableCollection<T>.ObserveClear<T>() Observable<Unit> IObservableCollection<T>.ObserveClear<T>()
Observable<(int Index, int Count)> IObservableCollection<T>.ObserveReverse<T>() Observable<(int Index, int Count)> IObservableCollection<T>.ObserveReverse<T>()
Observable<(int Index, int Count, IComparer<T> Comparer)> IObservableCollection<T>.ObserveSort<T>() Observable<(int Index, int Count, IComparer<T>? Comparer)> IObservableCollection<T>.ObserveSort<T>()
Observable<int> IObservableCollection<T>.ObserveCountChanged<T>() Observable<int> IObservableCollection<T>.ObserveCountChanged<T>()
``` ```
@ -355,6 +355,10 @@ public class WpfDispatcherCollection(Dispatcher dispatcher) : ICollectionEventDi
} }
``` ```
`ToNotifyCollectionChanged()` can also be called without going through a View. In this case, it's guaranteed that no filters will be applied, making it faster. If you want to apply filters, please generate a View before calling it. Additionally, `ObservableList` has a variation called `ToNotifyCollectionChangedSlim()`. This option doesn't generate a list for the View and shares the actual data, making it the fastest and most memory-efficient option. However, range operations such as `AddRange`, `InsertRange` and `RemoveRange` are not supported by WPF (or Avalonia), so they will throw runtime exceptions.
Views and ToNotifyCollectionChanged are internally connected by events, so they need to be `Dispose` to release those connections.
Unity Unity
--- ---
In Unity projects, you can installing `ObservableCollections` with [NugetForUnity](https://github.com/GlitchEnzo/NuGetForUnity). If R3 integration is required, similarly install `ObservableCollections.R3` via NuGetForUnity. In Unity projects, you can installing `ObservableCollections` with [NugetForUnity](https://github.com/GlitchEnzo/NuGetForUnity). If R3 integration is required, similarly install `ObservableCollections.R3` via NuGetForUnity.
@ -376,6 +380,10 @@ public class SampleScript : MonoBehaviour
{ {
var item = GameObject.Instantiate(prefab); var item = GameObject.Instantiate(prefab);
item.GetComponentInChildren<Text>().text = x.ToString(); item.GetComponentInChildren<Text>().text = x.ToString();
// add to root
item.transform.SetParent(root.transform);
return item.gameObject; return item.gameObject;
}); });
view.ViewChanged += View_ViewChanged; view.ViewChanged += View_ViewChanged;
@ -383,14 +391,14 @@ public class SampleScript : MonoBehaviour
void View_ViewChanged(in SynchronizedViewChangedEventArgs<int, string> eventArgs) void View_ViewChanged(in SynchronizedViewChangedEventArgs<int, string> eventArgs)
{ {
if (eventArgs.Action == NotifyCollectionChangedAction.Add) // hook remove event
{ if (NotifyCollectionChangedAction.Remove)
eventArgs.NewItem.View.transform.SetParent(root.transform);
}
else if (NotifyCollectionChangedAction.Remove)
{ {
GameObject.Destroy(eventArgs.OldItem.View); GameObject.Destroy(eventArgs.OldItem.View);
} }
// hook for Filter attached, clear, etc...
// if (NotifyCollectionChangedAction.Reset) { }
} }
void OnDestroy() void OnDestroy()

View File

@ -8,6 +8,13 @@
<StackPanel> <StackPanel>
<ListBox ItemsSource="{Binding ItemsView}" x:CompileBindings="False" /> <ListBox ItemsSource="{Binding ItemsView}" x:CompileBindings="False" />
<Button Content="Add" Command="{Binding AddCommand}" x:CompileBindings="False"/> <Button Content="Add" Command="{Binding AddCommand}" x:CompileBindings="False"/>
<Button Content="AddRange" Command="{Binding AddRangeCommand}" x:CompileBindings="False"/>
<Button Content="Insert" Command="{Binding InsertAtRandomCommand}" x:CompileBindings="False" />
<Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" x:CompileBindings="False" />
<Button Content="Clear" Command="{Binding ClearCommand}" x:CompileBindings="False"/> <Button Content="Clear" Command="{Binding ClearCommand}" x:CompileBindings="False"/>
<Button Content="Reverse" Command="{Binding ReverseCommand}" x:CompileBindings="False" />
<Button Content="Sort" Command="{Binding SortCommand}" x:CompileBindings="False"/>
<Button Content="AttachFilter" Command="{Binding AttachFilterCommand}" x:CompileBindings="False" />
<Button Content="ResetFilter" Command="{Binding ResetFilterCommand}" x:CompileBindings="False" />
</StackPanel> </StackPanel>
</Window> </Window>

View File

@ -26,22 +26,27 @@ namespace AvaloniaApp
private ObservableList<int> observableList { get; } = new ObservableList<int>(); private ObservableList<int> observableList { get; } = new ObservableList<int>();
public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; } public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; }
public ReactiveCommand<Unit> AddCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> AddCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> AddRangeCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> InsertAtRandomCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> RemoveAtRandomCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ClearCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> ClearCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ReverseCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> SortCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> AttachFilterCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ResetFilterCommand { get; } = new ReactiveCommand<Unit>();
public ViewModel() public ViewModel()
{ {
observableList.Add(1); observableList.Add(1);
observableList.Add(2); observableList.Add(2);
//ItemsView = observableList.CreateView(x => x).ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current); var view = observableList.CreateView(x => x);
ItemsView = view.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
ItemsView = observableList.CreateView(x => x).ToNotifyCollectionChanged();
//var test = ItemsView.ToArray(); // check for optimize list
//INotifyCollectionChangedSynchronizedView<int> // ItemsView = observableList.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
// ItemsView = observableList.CreateView(x => x).ToNotifyCollectionChanged();
// BindingOperations.EnableCollectionSynchronization(ItemsView, new object());
AddCommand.Subscribe(_ => AddCommand.Subscribe(_ =>
{ {
@ -51,12 +56,49 @@ namespace AvaloniaApp
}); });
}); });
// var iii = 10; AddRangeCommand.Subscribe(_ =>
{
var xs = Enumerable.Range(1, 5).Select(_ => Random.Shared.Next()).ToArray();
observableList.AddRange(xs);
});
InsertAtRandomCommand.Subscribe(_ =>
{
var from = Random.Shared.Next(0, view.Count);
observableList.Insert(from, Random.Shared.Next());
});
RemoveAtRandomCommand.Subscribe(_ =>
{
var from = Random.Shared.Next(0, view.Count);
observableList.RemoveAt(from);
});
ClearCommand.Subscribe(_ => ClearCommand.Subscribe(_ =>
{ {
// observableList.Add(iii++);
observableList.Clear(); observableList.Clear();
}); });
ReverseCommand.Subscribe(_ =>
{
observableList.Reverse();
});
SortCommand.Subscribe(_ =>
{
observableList.Sort();
});
AttachFilterCommand.Subscribe(_ =>
{
view.AttachFilter(x => x % 2 == 0);
});
ResetFilterCommand.Subscribe(_ =>
{
view.ResetFilter();
});
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using R3; using R3;
using System.Linq; using System.Linq;
@ -7,31 +7,10 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
var dict = new ObservableDictionary<int, string>();
var buffer = new ObservableList<int>(5); var view = dict.CreateView(x => x).ToNotifyCollectionChanged();
dict.Add(key: 1, value: "foo");
var view = buffer.CreateView(value => value); dict.Add(key: 2, value: "bar");
view.AttachFilter(value => value % 2 == 1); // when filtered, mismatch...!
//{
// INotifyCollectionChangedSynchronizedViewList created from ISynchronizedView with a filter.
var collection = view.ToNotifyCollectionChanged();
// Not disposed here.
//}
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);
buffer.Insert(0, 8);
buffer.Insert(0, 13);
buffer.Move(1, 5);
foreach (var item in view) foreach (var item in view)
{ {
@ -39,7 +18,8 @@ foreach (var item in view)
} }
Console.WriteLine("---"); Console.WriteLine("---");
foreach (var item in collection)
class ViewModel
{ {
Console.WriteLine(item); Console.WriteLine(item);

View File

@ -20,8 +20,10 @@
<StackPanel> <StackPanel>
<ListBox ItemsSource="{Binding ItemsView}" /> <ListBox ItemsSource="{Binding ItemsView}" />
<Button Content="Add" Command="{Binding AddCommand}" /> <Button Content="Add" Command="{Binding AddCommand}" />
<Button Content="AddRange" Command="{Binding AddRangeCommand}" />
<Button Content="Insert" Command="{Binding InsertAtRandomCommand}" /> <Button Content="Insert" Command="{Binding InsertAtRandomCommand}" />
<Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" /> <Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" />
<Button Content="RemoveRange" Command="{Binding RemoveRangeCommand}" />
<Button Content="Clear" Command="{Binding ClearCommand}" /> <Button Content="Clear" Command="{Binding ClearCommand}" />
<Button Content="Reverse" Command="{Binding ReverseCommand}" /> <Button Content="Reverse" Command="{Binding ReverseCommand}" />
<Button Content="Sort" Command="{Binding SortCommand}" /> <Button Content="Sort" Command="{Binding SortCommand}" />

View File

@ -2,6 +2,7 @@
using R3; using R3;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -76,8 +77,10 @@ namespace WpfApp
private ObservableList<int> observableList { get; } = new ObservableList<int>(); private ObservableList<int> observableList { get; } = new ObservableList<int>();
public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; } public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; }
public ReactiveCommand<Unit> AddCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> AddCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> AddRangeCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> InsertAtRandomCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> InsertAtRandomCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> RemoveAtRandomCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> RemoveAtRandomCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> RemoveRangeCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ClearCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> ClearCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ReverseCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> ReverseCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> SortCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> SortCommand { get; } = new ReactiveCommand<Unit>();
@ -90,8 +93,8 @@ namespace WpfApp
observableList.Add(2); observableList.Add(2);
var view = observableList.CreateView(x => x); var view = observableList.CreateView(x => x);
ItemsView = view.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current); //ItemsView = view.ToNotifyCollectionChanged();
ItemsView = observableList.ToNotifyCollectionChanged();
// check for optimize list // check for optimize list
// ItemsView = observableList.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current); // ItemsView = observableList.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
@ -99,10 +102,16 @@ namespace WpfApp
AddCommand.Subscribe(_ => AddCommand.Subscribe(_ =>
{ {
ThreadPool.QueueUserWorkItem(_ => // ThreadPool.QueueUserWorkItem(_ =>
{ {
observableList.Add(Random.Shared.Next()); observableList.Add(Random.Shared.Next());
}
}); });
AddRangeCommand.Subscribe(_ =>
{
var xs = Enumerable.Range(1, 5).Select(_ => Random.Shared.Next()).ToArray();
observableList.AddRange(xs);
}); });
InsertAtRandomCommand.Subscribe(_ => InsertAtRandomCommand.Subscribe(_ =>
@ -117,6 +126,11 @@ namespace WpfApp
observableList.RemoveAt(from); observableList.RemoveAt(from);
}); });
RemoveRangeCommand.Subscribe(_ =>
{
observableList.RemoveRange(2, 5);
});
ClearCommand.Subscribe(_ => ClearCommand.Subscribe(_ =>
{ {
observableList.Clear(); observableList.Clear();

View File

@ -71,7 +71,7 @@ public static class ObservableCollectionR3Extensions
return new ObservableCollectionReverse<T>(source, cancellationToken); return new ObservableCollectionReverse<T>(source, cancellationToken);
} }
public static Observable<(int Index, int Count, IComparer<T> Comparer)> ObserveSort<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default) public static Observable<(int Index, int Count, IComparer<T>? Comparer)> ObserveSort<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
{ {
return new ObservableCollectionSort<T>(source, cancellationToken); return new ObservableCollectionSort<T>(source, cancellationToken);
} }
@ -290,18 +290,18 @@ sealed class ObservableCollectionReverse<T>(IObservableCollection<T> collection,
} }
} }
sealed class ObservableCollectionSort<T>(IObservableCollection<T> collection, CancellationToken cancellationToken) : Observable<(int Index, int Count, IComparer<T> Comparer)> sealed class ObservableCollectionSort<T>(IObservableCollection<T> collection, CancellationToken cancellationToken) : Observable<(int Index, int Count, IComparer<T>? Comparer)>
{ {
protected override IDisposable SubscribeCore(Observer<(int Index, int Count, IComparer<T> Comparer)> observer) protected override IDisposable SubscribeCore(Observer<(int Index, int Count, IComparer<T>? Comparer)> observer)
{ {
return new _ObservableCollectionSort(collection, observer, cancellationToken); return new _ObservableCollectionSort(collection, observer, cancellationToken);
} }
sealed class _ObservableCollectionSort( sealed class _ObservableCollectionSort(
IObservableCollection<T> collection, IObservableCollection<T> collection,
Observer<(int Index, int Count, IComparer<T> Comparer)> observer, Observer<(int Index, int Count, IComparer<T>? Comparer)> observer,
CancellationToken cancellationToken) CancellationToken cancellationToken)
: ObservableCollectionObserverBase<T, (int Index, int Count, IComparer<T> Comparer)>(collection, observer, cancellationToken) : ObservableCollectionObserverBase<T, (int Index, int Count, IComparer<T>? Comparer)>(collection, observer, cancellationToken)
{ {
protected override void Handler(in NotifyCollectionChangedEventArgs<T> eventArgs) protected override void Handler(in NotifyCollectionChangedEventArgs<T> eventArgs)
{ {

View File

@ -194,7 +194,7 @@ public class AlternateIndexList<T> : IEnumerable<T>
object IEnumerator.Current => Current; object IEnumerator.Current => Current;
public void Dispose() => iter.Reset(); public void Dispose() => iter.Dispose();
public bool MoveNext() public bool MoveNext()
{ {
@ -207,7 +207,7 @@ public class AlternateIndexList<T> : IEnumerable<T>
return false; return false;
} }
public void Reset() => iter.Reset(); public void Reset() { }
public IEnumerator<IndexedValue> GetEnumerator() => this; public IEnumerator<IndexedValue> GetEnumerator() => this;

View File

@ -25,9 +25,9 @@ namespace ObservableCollections
Comparer = comparer ?? NullComparerSentinel.Instance; Comparer = comparer ?? NullComparerSentinel.Instance;
} }
public (int Index, int Count, IComparer<T> Comparer) AsTuple() public (int Index, int Count, IComparer<T>? Comparer) AsTuple()
{ {
return (Index, Count, Comparer!); return (Index, Count, Comparer);
} }
public static SortOperation<T> CreateReverse(int index, int count) public static SortOperation<T> CreateReverse(int index, int count)

View File

@ -21,12 +21,18 @@ public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableLis
// return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform)); // return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform));
//} //}
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged() /// <summary>
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
/// </summary>
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim()
{ {
return new ObservableListSynchronizedViewList<T>(this, null); return new ObservableListSynchronizedViewList<T>(this, null);
} }
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) /// <summary>
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
/// </summary>
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new ObservableListSynchronizedViewList<T>(this, collectionEventDispatcher); return new ObservableListSynchronizedViewList<T>(this, collectionEventDispatcher);
} }

View File

@ -1,10 +1,11 @@
using ObservableCollections.Internal; using ObservableCollections.Internal;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -16,6 +17,8 @@ internal class FiltableSynchronizedViewList<T, TView> : ISynchronizedViewList<TV
protected readonly AlternateIndexList<TView> listView; protected readonly AlternateIndexList<TView> listView;
protected readonly object gate = new object(); protected readonly object gate = new object();
protected virtual bool IsSupportRangeFeature => true;
public FiltableSynchronizedViewList(ISynchronizedView<T, TView> parent) public FiltableSynchronizedViewList(ISynchronizedView<T, TView> parent)
{ {
this.parent = parent; this.parent = parent;
@ -62,15 +65,39 @@ internal class FiltableSynchronizedViewList<T, TView> : ISynchronizedViewList<TV
case NotifyCollectionChangedAction.Add: // Add or Insert case NotifyCollectionChangedAction.Add: // Add or Insert
if (e.IsSingleItem) if (e.IsSingleItem)
{ {
var index = listView.Insert(e.NewStartingIndex, e.NewItem.View); if (e.NewStartingIndex == -1)
{
// add operation
var index = listView.Count;
listView.Insert(index, e.NewItem.View);
OnCollectionChanged(e.WithNewStartingIndex(index)); OnCollectionChanged(e.WithNewStartingIndex(index));
return; return;
} }
else else
{
var index = listView.Insert(e.NewStartingIndex, e.NewItem.View);
OnCollectionChanged(e.WithNewStartingIndex(index));
return;
}
}
else
{
if (IsSupportRangeFeature)
{ {
using var array = new CloneCollection<TView>(e.NewViews); using var array = new CloneCollection<TView>(e.NewViews);
var index = listView.InsertRange(e.NewStartingIndex, array.AsEnumerable()); var index = listView.InsertRange(e.NewStartingIndex, array.AsEnumerable());
OnCollectionChanged(e.WithNewStartingIndex(index)); OnCollectionChanged(e.WithNewStartingIndex(index));
}
else
{
var span = e.NewViews;
for (int i = 0; i < span.Length; i++)
{
var index = listView.Insert(e.NewStartingIndex + i, span[i]);
var ev = new SynchronizedViewChangedEventArgs<T, TView>(e.Action, true, newItem: (e.NewValues[i], span[i]), newStartingIndex: index);
OnCollectionChanged(ev);
}
}
return; return;
} }
case NotifyCollectionChangedAction.Remove: // Remove case NotifyCollectionChangedAction.Remove: // Remove
@ -99,9 +126,23 @@ internal class FiltableSynchronizedViewList<T, TView> : ISynchronizedViewList<TV
return; return;
} }
else else
{
if (IsSupportRangeFeature)
{ {
index = listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length); index = listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length);
} }
else
{
var span = e.OldViews;
for (int i = 0; i < span.Length; i++)
{
index = listView.RemoveAt(e.OldStartingIndex); // when removed, next remove index is same.
var ev = new SynchronizedViewChangedEventArgs<T, TView>(e.Action, true, oldItem: (e.OldValues[i], span[i]), oldStartingIndex: index);
OnCollectionChanged(ev);
}
return;
}
}
} }
OnCollectionChanged(e.WithOldStartingIndex(index)); OnCollectionChanged(e.WithOldStartingIndex(index));
return; return;
@ -214,7 +255,7 @@ internal class FiltableSynchronizedViewList<T, TView> : ISynchronizedViewList<TV
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return listView.GetEnumerator(); return GetEnumerator();
} }
public void Dispose() public void Dispose()
@ -230,6 +271,8 @@ internal class NonFilteredSynchronizedViewList<T, TView> : ISynchronizedViewList
protected readonly List<TView> listView; // no filter can be faster protected readonly List<TView> listView; // no filter can be faster
protected readonly object gate = new object(); protected readonly object gate = new object();
protected virtual bool IsSupportRangeFeature => true;
public NonFilteredSynchronizedViewList(ISynchronizedView<T, TView> parent) public NonFilteredSynchronizedViewList(ISynchronizedView<T, TView> parent)
{ {
this.parent = parent; this.parent = parent;
@ -250,10 +293,22 @@ internal class NonFilteredSynchronizedViewList<T, TView> : ISynchronizedViewList
case NotifyCollectionChangedAction.Add: // Add or Insert case NotifyCollectionChangedAction.Add: // Add or Insert
if (e.IsSingleItem) if (e.IsSingleItem)
{ {
listView.Insert(e.NewStartingIndex, e.NewItem.View); if (e.NewStartingIndex == -1)
{
var index = listView.Count;
listView.Add(e.NewItem.View);
OnCollectionChanged(e.WithNewStartingIndex(index));
return;
} }
else else
{ {
listView.Insert(e.NewStartingIndex, e.NewItem.View);
}
}
else
{
if (IsSupportRangeFeature)
{
#if NET8_0_OR_GREATER #if NET8_0_OR_GREATER
listView.InsertRange(e.NewStartingIndex, e.NewViews); listView.InsertRange(e.NewStartingIndex, e.NewViews);
#else #else
@ -261,6 +316,19 @@ internal class NonFilteredSynchronizedViewList<T, TView> : ISynchronizedViewList
listView.InsertRange(e.NewStartingIndex, array.AsEnumerable()); listView.InsertRange(e.NewStartingIndex, array.AsEnumerable());
#endif #endif
} }
else
{
var span = e.NewViews;
for (int i = 0; i < span.Length; i++)
{
var index = e.NewStartingIndex + i;
listView.Insert(index, span[i]);
var ev = new SynchronizedViewChangedEventArgs<T, TView>(e.Action, true, newItem: (e.NewValues[i], span[i]), newStartingIndex: index);
OnCollectionChanged(ev);
}
return;
}
}
break; break;
case NotifyCollectionChangedAction.Remove: // Remove case NotifyCollectionChangedAction.Remove: // Remove
{ {
@ -291,9 +359,23 @@ internal class NonFilteredSynchronizedViewList<T, TView> : ISynchronizedViewList
return; return;
} }
else else
{
if (IsSupportRangeFeature)
{ {
listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length); listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length);
} }
else
{
var span = e.OldViews;
for (int i = 0; i < span.Length; i++)
{
listView.RemoveAt(e.OldStartingIndex); // when removed, next remove index is same.
var ev = new SynchronizedViewChangedEventArgs<T, TView>(e.Action, true, oldItem: (e.OldValues[i], span[i]), oldStartingIndex: e.OldStartingIndex);
OnCollectionChanged(ev);
}
return;
}
}
} }
break; break;
} }
@ -343,9 +425,6 @@ internal class NonFilteredSynchronizedViewList<T, TView> : ISynchronizedViewList
} }
else else
{ {
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
#pragma warning disable CS0436 #pragma warning disable CS0436
if (parent is ObservableList<T>.View<TView> observableListView && typeof(T) == typeof(TView)) if (parent is ObservableList<T>.View<TView> observableListView && typeof(T) == typeof(TView))
@ -431,7 +510,7 @@ internal class NonFilteredSynchronizedViewList<T, TView> : ISynchronizedViewList
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return listView.GetEnumerator(); return GetEnumerator();
} }
public void Dispose() public void Dispose()
@ -451,6 +530,8 @@ internal class NotifyCollectionChangedSynchronizedViewList<T, TView> :
readonly ICollectionEventDispatcher eventDispatcher; readonly ICollectionEventDispatcher eventDispatcher;
protected override bool IsSupportRangeFeature => false; // WPF, Avalonia etc does not support range notification
public event NotifyCollectionChangedEventHandler? CollectionChanged; public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
@ -699,6 +780,8 @@ internal class NonFilteredNotifyCollectionChangedSynchronizedViewList<T, TView>
public event NotifyCollectionChangedEventHandler? CollectionChanged; public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
protected override bool IsSupportRangeFeature => false; // WPF, Avalonia etc does not support range notification
public NonFilteredNotifyCollectionChangedSynchronizedViewList(ISynchronizedView<T, TView> parent, ICollectionEventDispatcher? eventDispatcher) public NonFilteredNotifyCollectionChangedSynchronizedViewList(ISynchronizedView<T, TView> parent, ICollectionEventDispatcher? eventDispatcher)
: base(parent) : base(parent)
{ {