Merge pull request #25 from Cysharp/hadashiA/fix-notify-collection-changed
Change the INotifyCollectionChaneged to IE<TView> from IE<(T, View)>
This commit is contained in:
commit
4023a28e4f
@ -24,13 +24,14 @@ namespace ObservableCollections
|
||||
public interface ISynchronizedView<T, TView> : IReadOnlyCollection<(T Value, TView View)>, IDisposable
|
||||
{
|
||||
object SyncRoot { get; }
|
||||
ISynchronizedViewFilter<T, TView> CurrentFilter { get; }
|
||||
|
||||
event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||
event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
void AttachFilter(ISynchronizedViewFilter<T, TView> filter, bool invokeAddEventForInitialElements = false);
|
||||
void ResetFilter(Action<T, TView>? resetAction);
|
||||
INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged();
|
||||
INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged();
|
||||
}
|
||||
|
||||
public interface ISortableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
|
||||
@ -44,7 +45,7 @@ namespace ObservableCollections
|
||||
//{
|
||||
//}
|
||||
|
||||
public interface INotifyCollectionChangedSynchronizedView<T, TView> : ISynchronizedView<T, TView>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
public interface INotifyCollectionChangedSynchronizedView<out TView> : IReadOnlyCollection<TView>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,11 @@ namespace ObservableCollections.Internal
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||
|
||||
@ -107,7 +112,7 @@ namespace ObservableCollections.Internal
|
||||
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
@ -119,6 +124,11 @@ namespace ObservableCollections.Internal
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||
|
||||
@ -206,7 +216,7 @@ namespace ObservableCollections.Internal
|
||||
Array.Sort(array, new TViewComparer(viewComparer));
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||
}
|
||||
|
@ -6,38 +6,22 @@ using System.ComponentModel;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class NotifyCollectionChangedSynchronizedView<T, TView> : INotifyCollectionChangedSynchronizedView<T, TView>
|
||||
internal class NotifyCollectionChangedSynchronizedView<T, TView> :
|
||||
INotifyCollectionChangedSynchronizedView<TView>,
|
||||
ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count");
|
||||
|
||||
readonly ISynchronizedView<T, TView> parent;
|
||||
static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new PropertyChangedEventArgs("Count");
|
||||
readonly ISynchronizedViewFilter<T, TView> currentFilter;
|
||||
|
||||
public NotifyCollectionChangedSynchronizedView(ISynchronizedView<T, TView> parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.parent.RoutingCollectionChanged += Parent_RoutingCollectionChanged;
|
||||
currentFilter = parent.CurrentFilter;
|
||||
parent.AttachFilter(this);
|
||||
}
|
||||
|
||||
private void Parent_RoutingCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
|
||||
{
|
||||
CollectionChanged?.Invoke(this, e.ToStandardEventArgs());
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
// add, remove, reset will change the count.
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
PropertyChanged?.Invoke(this, CountPropertyChangedEventArgs);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public object SyncRoot => parent.SyncRoot;
|
||||
|
||||
public int Count => parent.Count;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||
@ -55,16 +39,43 @@ namespace ObservableCollections.Internal
|
||||
remove { parent.RoutingCollectionChanged -= value; }
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter, bool invokeAddEventForCurrentElements = false) => parent.AttachFilter(filter, invokeAddEventForCurrentElements);
|
||||
public void ResetFilter(Action<T, TView>? resetAction) => parent.ResetFilter(resetAction);
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged() => this;
|
||||
public void Dispose()
|
||||
{
|
||||
this.parent.RoutingCollectionChanged -= Parent_RoutingCollectionChanged;
|
||||
parent.Dispose();
|
||||
}
|
||||
|
||||
public IEnumerator<(T, TView)> GetEnumerator() => parent.GetEnumerator();
|
||||
public IEnumerator<TView> GetEnumerator()
|
||||
{
|
||||
foreach (var (value, view) in parent)
|
||||
{
|
||||
yield return view;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => parent.GetEnumerator();
|
||||
|
||||
public bool IsMatch(T value, TView view) => currentFilter.IsMatch(value, view);
|
||||
public void WhenTrue(T value, TView view) => currentFilter.WhenTrue(value, view);
|
||||
public void WhenFalse(T value, TView view) => currentFilter.WhenFalse(value, view);
|
||||
|
||||
public void OnCollectionChanged(ChangedKind changedKind, T value, TView view, in NotifyCollectionChangedEventArgs<T> eventArgs)
|
||||
{
|
||||
currentFilter.OnCollectionChanged(changedKind, value, view, in eventArgs);
|
||||
|
||||
switch (changedKind)
|
||||
{
|
||||
case ChangedKind.Add:
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, view, eventArgs.NewStartingIndex));
|
||||
return;
|
||||
case ChangedKind.Remove:
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, view, eventArgs.OldStartingIndex));
|
||||
break;
|
||||
case ChangedKind.Move:
|
||||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, view, eventArgs.NewStartingIndex, eventArgs.OldStartingIndex));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(changedKind), changedKind, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,17 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class SortedView<T, TKey, TView> : ISynchronizedView<T, TView>
|
||||
where TKey : notnull
|
||||
{
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
readonly IObservableCollection<T> source;
|
||||
readonly Func<T, TView> transform;
|
||||
readonly Func<T, TKey> identitySelector;
|
||||
@ -85,7 +89,7 @@ namespace ObservableCollections.Internal
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
@ -192,7 +196,7 @@ namespace ObservableCollections.Internal
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((value, id), (value, view));
|
||||
var newIndex = list.IndexOfKey((value, id));
|
||||
var newIndex = list.IndexOfKey((value, id));
|
||||
|
||||
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, newIndex));
|
||||
}
|
||||
|
@ -22,6 +22,11 @@ namespace ObservableCollections.Internal
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
public SortedViewViewComparer(IObservableCollection<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> comparer)
|
||||
{
|
||||
this.source = source;
|
||||
@ -89,7 +94,7 @@ namespace ObservableCollections.Internal
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
@ -146,7 +151,7 @@ namespace ObservableCollections.Internal
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
var index = list.IndexOfKey((view, id));
|
||||
var index = list.IndexOfKey((view, id));
|
||||
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
|
||||
<!-- NuGet Packaging -->
|
||||
|
@ -14,7 +14,7 @@ namespace ObservableCollections
|
||||
// reverse is no used.
|
||||
return new View<TView>(this, transform);
|
||||
}
|
||||
|
||||
|
||||
class View<TView> : ISynchronizedView<KeyValuePair<TKey, TValue>, TView>
|
||||
{
|
||||
readonly ObservableDictionary<TKey, TValue> source;
|
||||
@ -39,6 +39,11 @@ namespace ObservableCollections
|
||||
public event NotifyCollectionChangedEventHandler<KeyValuePair<TKey, TValue>>? RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
@ -91,7 +96,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<KeyValuePair<TKey, TValue>, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
|
@ -16,6 +16,11 @@ namespace ObservableCollections
|
||||
|
||||
sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
readonly ObservableHashSet<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly Dictionary<T, (T, TView)> dict;
|
||||
@ -85,7 +90,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
|
@ -16,6 +16,14 @@ namespace ObservableCollections
|
||||
|
||||
sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot) { return filter; }
|
||||
}
|
||||
}
|
||||
|
||||
readonly ObservableList<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly bool reverse;
|
||||
@ -89,7 +97,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
|
@ -62,7 +62,6 @@ namespace ObservableCollections
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T>? CollectionChanged;
|
||||
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
|
@ -28,6 +28,11 @@ namespace ObservableCollections
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
public View(ObservableQueue<T> source, Func<T, TView> selector, bool reverse)
|
||||
{
|
||||
this.source = source;
|
||||
@ -89,7 +94,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
|
@ -17,6 +17,11 @@ namespace ObservableCollections
|
||||
// used with ObservableFixedSizeRingBuffer
|
||||
internal sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
readonly IObservableCollection<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly bool reverse;
|
||||
@ -89,7 +94,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
|
@ -28,6 +28,11 @@ namespace ObservableCollections
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public ISynchronizedViewFilter<T, TView> CurrentFilter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
|
||||
public View(ObservableStack<T> source, Func<T, TView> selector, bool reverse)
|
||||
{
|
||||
this.source = source;
|
||||
@ -89,7 +94,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
|
@ -0,0 +1,57 @@
|
||||
namespace ObservableCollections.Tests;
|
||||
|
||||
public class ToNotifyCollectionChangedTest
|
||||
{
|
||||
[Fact]
|
||||
public void ToNotifyCollectionChanged()
|
||||
{
|
||||
var list = new ObservableList<int>();
|
||||
|
||||
list.Add(10);
|
||||
list.Add(20);
|
||||
list.Add(30);
|
||||
|
||||
var notify = list.CreateView(x => $"${x}").ToNotifyCollectionChanged();
|
||||
|
||||
list.Add(40);
|
||||
list.Add(50);
|
||||
|
||||
using var e = notify.GetEnumerator();
|
||||
e.MoveNext().Should().BeTrue();
|
||||
e.Current.Should().Be("$10");
|
||||
e.MoveNext().Should().BeTrue();
|
||||
e.Current.Should().Be("$20");
|
||||
e.MoveNext().Should().BeTrue();
|
||||
e.Current.Should().Be("$30");
|
||||
e.MoveNext().Should().BeTrue();
|
||||
e.Current.Should().Be("$40");
|
||||
e.MoveNext().Should().BeTrue();
|
||||
e.Current.Should().Be("$50");
|
||||
e.MoveNext().Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToNotifyCollectionChanged_Filter()
|
||||
{
|
||||
var list = new ObservableList<int>();
|
||||
|
||||
list.Add(1);
|
||||
list.Add(2);
|
||||
list.Add(5);
|
||||
list.Add(3);
|
||||
|
||||
var view = list.CreateView(x => $"${x}");
|
||||
var notify = view.ToNotifyCollectionChanged();
|
||||
|
||||
view.AttachFilter((value, view) => value % 2 == 0);
|
||||
|
||||
list.Add(4);
|
||||
|
||||
using var e = notify.GetEnumerator();
|
||||
e.MoveNext().Should().BeTrue();
|
||||
e.Current.Should().Be("$2");
|
||||
e.MoveNext().Should().BeTrue();
|
||||
e.Current.Should().Be("$4");
|
||||
e.MoveNext().Should().BeFalse();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user