Add R3 ObserveChanged

This commit is contained in:
neuecc 2024-10-16 14:22:59 +09:00
parent 8b75c41da7
commit 8913192459
5 changed files with 143 additions and 125 deletions

View File

@ -50,6 +50,7 @@ ObservableCollections has not just a simple list, there are many more data struc
If you want to handle each change event with Rx, you can monitor it with the following method by combining it with [R3](https://github.com/Cysharp/R3): If you want to handle each change event with Rx, you can monitor it with the following method by combining it with [R3](https://github.com/Cysharp/R3):
```csharp ```csharp
Observable<CollectionChangedEvent<T>> IObservableCollection<T>.ObserveChanged()
Observable<CollectionAddEvent<T>> IObservableCollection<T>.ObserveAdd() Observable<CollectionAddEvent<T>> IObservableCollection<T>.ObserveAdd()
Observable<CollectionRemoveEvent<T>> IObservableCollection<T>.ObserveRemove() Observable<CollectionRemoveEvent<T>> IObservableCollection<T>.ObserveRemove()
Observable<CollectionReplaceEvent<T>> IObservableCollection<T>.ObserveReplace() Observable<CollectionReplaceEvent<T>> IObservableCollection<T>.ObserveReplace()
@ -237,7 +238,7 @@ class DescendantComaprer : IComparer<int>
Reactive Extensions with R3 Reactive Extensions with R3
--- ---
Once the R3 extension package is installed, you can subscribe to `ObserveAdd`, `ObserveRemove`, `ObserveReplace`, `ObserveMove`, `ObserveReset`, `ObserveClear`, `ObserveReverse`, `ObserveSort` events as Rx, allowing you to compose events individually. Once the R3 extension package is installed, you can subscribe to `ObserveChanged`, `ObserveAdd`, `ObserveRemove`, `ObserveReplace`, `ObserveMove`, `ObserveReset`, `ObserveClear`, `ObserveReverse`, `ObserveSort` events as Rx, allowing you to compose events individually.
> dotnet add package [ObservableCollections.R3](https://www.nuget.org/packages/ObservableCollections.R3) > dotnet add package [ObservableCollections.R3](https://www.nuget.org/packages/ObservableCollections.R3)
@ -317,7 +318,7 @@ Because of data binding in WPF, it is important that the collection is Observabl
// WPF simple sample. // WPF simple sample.
ObservableList<int> list; ObservableList<int> list;
public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; set; } public NotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; set; }
public MainWindow() public MainWindow()
{ {
@ -366,8 +367,8 @@ public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T ori
public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView> public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
{ {
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter); NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher); NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
} }
``` ```
@ -564,8 +565,8 @@ public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisp
void AttachFilter(ISynchronizedViewFilter<T> filter); void AttachFilter(ISynchronizedViewFilter<T> filter);
void ResetFilter(); void ResetFilter();
ISynchronizedViewList<TView> ToViewList(); ISynchronizedViewList<TView> ToViewList();
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(); NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
} }
``` ```
@ -630,10 +631,10 @@ public sealed partial class ObservableList<T>
{ {
public IWritableSynchronizedView<T, TView> CreateWritableView<TView>(Func<T, TView> transform); public IWritableSynchronizedView<T, TView> CreateWritableView<TView>(Func<T, TView> transform);
public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(); public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged();
public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter); public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter);
public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher, WritableViewChangedEventHandler<T, TView>? converter); public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher, WritableViewChangedEventHandler<T, TView>? converter);
} }
public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T originalValue, ref bool setValue); public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T originalValue, ref bool setValue);
@ -671,9 +672,18 @@ public interface ISynchronizedViewList<out TView> : IReadOnlyList<TView>, IDispo
{ {
} }
// Obsolete for public use
public interface INotifyCollectionChangedSynchronizedViewList<out TView> : ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged public interface INotifyCollectionChangedSynchronizedViewList<out TView> : ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
{ {
} }
public abstract class NotifyCollectionChangedSynchronizedViewList<TView> :
INotifyCollectionChangedSynchronizedViewList<TView>,
IWritableSynchronizedViewList<TView>,
IList<TView>,
IList
{
}
``` ```
License License

View File

@ -39,6 +39,11 @@ var bindable = view.ToWritableNotifyCollectionChanged((string? newView, Person o
} }
}); });
list.Clear();
list.Add(new() { Age = 99, Name = "tako" });
// bindable[0] = "takoyaki"; // bindable[0] = "takoyaki";
foreach (var item in view) foreach (var item in view)

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using R3; using R3;
@ -27,15 +28,40 @@ public readonly record struct CollectionResetEvent<T>
} }
} }
[StructLayout(LayoutKind.Auto)]
public readonly record struct CollectionChangedEvent<T>
{
public readonly NotifyCollectionChangedAction Action;
public readonly T NewItem;
public readonly T OldItem;
public readonly int NewStartingIndex;
public readonly int OldStartingIndex;
public readonly SortOperation<T> SortOperation;
public CollectionChangedEvent(NotifyCollectionChangedAction action, T newItem, T oldItem, int newStartingIndex, int oldStartingIndex, SortOperation<T> sortOperation)
{
Action = action;
NewItem = newItem;
OldItem = oldItem;
NewStartingIndex = newStartingIndex;
OldStartingIndex = oldStartingIndex;
SortOperation = sortOperation;
}
}
public readonly record struct DictionaryAddEvent<TKey, TValue>(TKey Key, TValue Value); public readonly record struct DictionaryAddEvent<TKey, TValue>(TKey Key, TValue Value);
public readonly record struct DictionaryRemoveEvent<TKey, TValue>(TKey Key, TValue Value); public readonly record struct DictionaryRemoveEvent<TKey, TValue>(TKey Key, TValue Value);
public readonly record struct DictionaryReplaceEvent<TKey, TValue>(TKey Key, TValue OldValue, TValue NewValue); public readonly record struct DictionaryReplaceEvent<TKey, TValue>(TKey Key, TValue OldValue, TValue NewValue);
public static class ObservableCollectionR3Extensions public static class ObservableCollectionR3Extensions
{ {
public static Observable<CollectionChangedEvent<T>> ObserveChanged<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
{
return new ObservableCollectionChanged<T>(source, cancellationToken);
}
public static Observable<CollectionAddEvent<T>> ObserveAdd<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default) public static Observable<CollectionAddEvent<T>> ObserveAdd<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
{ {
return new ObservableCollectionAdd<T>(source, cancellationToken); return new ObservableCollectionAdd<T>(source, cancellationToken);
@ -102,6 +128,72 @@ public static class ObservableDictionaryR3Extensions
} }
} }
sealed class ObservableCollectionChanged<T>(IObservableCollection<T> collection, CancellationToken cancellationToken)
: Observable<CollectionChangedEvent<T>>
{
protected override IDisposable SubscribeCore(Observer<CollectionChangedEvent<T>> observer)
{
return new _ObservableCollectionAdd(collection, observer, cancellationToken);
}
sealed class _ObservableCollectionAdd(
IObservableCollection<T> collection,
Observer<CollectionChangedEvent<T>> observer,
CancellationToken cancellationToken)
: ObservableCollectionObserverBase<T, CollectionChangedEvent<T>>(collection, observer, cancellationToken)
{
protected override void Handler(in NotifyCollectionChangedEventArgs<T> eventArgs)
{
if (eventArgs.IsSingleItem)
{
var newArgs = new CollectionChangedEvent<T>(
eventArgs.Action,
eventArgs.NewItem,
eventArgs.OldItem,
eventArgs.NewStartingIndex,
eventArgs.OldStartingIndex,
eventArgs.SortOperation);
observer.OnNext(newArgs);
}
else
{
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
{
var i = eventArgs.NewStartingIndex;
foreach (var item in eventArgs.NewItems)
{
var newArgs = new CollectionChangedEvent<T>(
eventArgs.Action,
eventArgs.NewItem,
eventArgs.OldItem,
i++,
eventArgs.OldStartingIndex,
eventArgs.SortOperation);
observer.OnNext(newArgs);
}
}
else if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in eventArgs.OldItems)
{
var newArgs = new CollectionChangedEvent<T>(
eventArgs.Action,
eventArgs.NewItem,
eventArgs.OldItem,
eventArgs.NewStartingIndex,
eventArgs.OldStartingIndex, // removed, uses same index
eventArgs.SortOperation);
observer.OnNext(newArgs);
}
}
}
}
}
}
sealed class ObservableCollectionAdd<T>(IObservableCollection<T> collection, CancellationToken cancellationToken) sealed class ObservableCollectionAdd<T>(IObservableCollection<T> collection, CancellationToken cancellationToken)
: Observable<CollectionAddEvent<T>> : Observable<CollectionAddEvent<T>>
{ {
@ -161,10 +253,9 @@ sealed class ObservableCollectionRemove<T>(IObservableCollection<T> collection,
} }
else else
{ {
var i = eventArgs.OldStartingIndex;
foreach (var item in eventArgs.OldItems) foreach (var item in eventArgs.OldItems)
{ {
observer.OnNext(new CollectionRemoveEvent<T>(i++, item)); observer.OnNext(new CollectionRemoveEvent<T>(eventArgs.OldStartingIndex, item)); // remove uses same index
} }
} }
} }

View File

@ -24,7 +24,7 @@ public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
/// <summary> /// <summary>
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range. /// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
/// </summary> /// </summary>
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim() public NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim()
{ {
return new ObservableListSynchronizedViewList<T>(this, null); return new ObservableListSynchronizedViewList<T>(this, null);
} }
@ -32,7 +32,7 @@ public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
/// <summary> /// <summary>
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range. /// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
/// </summary> /// </summary>
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim(ICollectionEventDispatcher? collectionEventDispatcher) public NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return new ObservableListSynchronizedViewList<T>(this, collectionEventDispatcher); return new ObservableListSynchronizedViewList<T>(this, collectionEventDispatcher);
} }
@ -48,7 +48,7 @@ public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
//} //}
} }
internal sealed class ObservableListSynchronizedViewList<T> : INotifyCollectionChangedSynchronizedViewList<T>, IList<T>, IList internal sealed class ObservableListSynchronizedViewList<T> : NotifyCollectionChangedSynchronizedViewList<T>
{ {
static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count");
static readonly Action<NotifyCollectionChangedEventArgs> raiseChangedEventInvoke = RaiseChangedEvent; static readonly Action<NotifyCollectionChangedEventArgs> raiseChangedEventInvoke = RaiseChangedEvent;
@ -56,8 +56,8 @@ internal sealed class ObservableListSynchronizedViewList<T> : INotifyCollectionC
readonly ObservableList<T> parent; readonly ObservableList<T> parent;
readonly ICollectionEventDispatcher eventDispatcher; readonly ICollectionEventDispatcher eventDispatcher;
public event NotifyCollectionChangedEventHandler? CollectionChanged; public override event NotifyCollectionChangedEventHandler? CollectionChanged;
public event PropertyChangedEventHandler? PropertyChanged; public override event PropertyChangedEventHandler? PropertyChanged;
public ObservableListSynchronizedViewList(ObservableList<T> parent, ICollectionEventDispatcher? eventDispatcher) public ObservableListSynchronizedViewList(ObservableList<T> parent, ICollectionEventDispatcher? eventDispatcher)
{ {
@ -161,130 +161,42 @@ internal sealed class ObservableListSynchronizedViewList<T> : INotifyCollectionC
} }
} }
public T this[int index] => parent[index]; public override T this[int index]
{
get
{
return parent[index];
}
set
{
parent[index] = value;
}
}
public int Count => parent.Count; public override int Count => parent.Count;
public IEnumerator<T> GetEnumerator() public override IEnumerator<T> GetEnumerator()
{ {
return parent.GetEnumerator(); return parent.GetEnumerator();
} }
IEnumerator IEnumerable.GetEnumerator() public override void Dispose()
{
return parent.GetEnumerator();
}
public void Dispose()
{ {
parent.CollectionChanged -= Parent_CollectionChanged; parent.CollectionChanged -= Parent_CollectionChanged;
} }
// IList<T>, IList implementation public override void Add(T item)
T IList<T>.this[int index]
{ {
get => ((IReadOnlyList<T>)this)[index]; parent.Add(item);
set => throw new NotSupportedException();
} }
object? IList.this[int index] public override bool Contains(T item)
{
get
{
return this[index];
}
set => throw new NotSupportedException();
}
static bool IsCompatibleObject(object? value)
{
return value is T || value == null && default(T) == null;
}
public bool IsReadOnly => true;
public bool IsFixedSize => false;
public bool IsSynchronized => true;
public object SyncRoot => parent.SyncRoot;
public void Add(T item)
{
throw new NotSupportedException();
}
public int Add(object? value)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Contains(T item)
{ {
return parent.Contains(item); return parent.Contains(item);
} }
public bool Contains(object? value) public override int IndexOf(T item)
{
if (IsCompatibleObject(value))
{
return Contains((T)value!);
}
return false;
}
public void CopyTo(T[] array, int arrayIndex)
{
throw new NotSupportedException();
}
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
public int IndexOf(T item)
{ {
return parent.IndexOf(item); return parent.IndexOf(item);
} }
public int IndexOf(object? item)
{
if (IsCompatibleObject(item))
{
return IndexOf((T)item!);
}
return -1;
}
public void Insert(int index, T item)
{
throw new NotSupportedException();
}
public void Insert(int index, object? value)
{
throw new NotImplementedException();
}
public bool Remove(T item)
{
throw new NotSupportedException();
}
public void Remove(object? value)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotSupportedException();
}
} }

View File

@ -174,7 +174,7 @@ namespace ObservableCollections
} }
else else
{ {
ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1); ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1);
} }
} }
} }