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):
```csharp
Observable<CollectionChangedEvent<T>> IObservableCollection<T>.ObserveChanged()
Observable<CollectionAddEvent<T>> IObservableCollection<T>.ObserveAdd()
Observable<CollectionRemoveEvent<T>> IObservableCollection<T>.ObserveRemove()
Observable<CollectionReplaceEvent<T>> IObservableCollection<T>.ObserveReplace()
@ -237,7 +238,7 @@ class DescendantComaprer : IComparer<int>
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)
@ -317,7 +318,7 @@ Because of data binding in WPF, it is important that the collection is Observabl
// WPF simple sample.
ObservableList<int> list;
public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; set; }
public NotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; set; }
public MainWindow()
{
@ -366,8 +367,8 @@ public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T ori
public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
{
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
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 ResetFilter();
ISynchronizedViewList<TView> ToViewList();
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
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 INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged();
public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
public INotifyCollectionChangedSynchronizedViewList<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<T> ToWritableNotifyCollectionChanged();
public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, 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);
@ -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 abstract class NotifyCollectionChangedSynchronizedViewList<TView> :
INotifyCollectionChangedSynchronizedViewList<TView>,
IWritableSynchronizedViewList<TView>,
IList<TView>,
IList
{
}
```
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";
foreach (var item in view)

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Threading;
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 DictionaryRemoveEvent<TKey, TValue>(TKey Key, TValue Value);
public readonly record struct DictionaryReplaceEvent<TKey, TValue>(TKey Key, TValue OldValue, TValue NewValue);
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)
{
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)
: Observable<CollectionAddEvent<T>>
{
@ -161,10 +253,9 @@ sealed class ObservableCollectionRemove<T>(IObservableCollection<T> collection,
}
else
{
var i = eventArgs.OldStartingIndex;
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>
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
/// </summary>
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim()
public NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim()
{
return new ObservableListSynchronizedViewList<T>(this, null);
}
@ -32,7 +32,7 @@ public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
/// <summary>
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
/// </summary>
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim(ICollectionEventDispatcher? collectionEventDispatcher)
public NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim(ICollectionEventDispatcher? 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 Action<NotifyCollectionChangedEventArgs> raiseChangedEventInvoke = RaiseChangedEvent;
@ -56,8 +56,8 @@ internal sealed class ObservableListSynchronizedViewList<T> : INotifyCollectionC
readonly ObservableList<T> parent;
readonly ICollectionEventDispatcher eventDispatcher;
public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event PropertyChangedEventHandler? PropertyChanged;
public override event NotifyCollectionChangedEventHandler? CollectionChanged;
public override event PropertyChangedEventHandler? PropertyChanged;
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();
}
IEnumerator IEnumerable.GetEnumerator()
{
return parent.GetEnumerator();
}
public void Dispose()
public override void Dispose()
{
parent.CollectionChanged -= Parent_CollectionChanged;
}
// IList<T>, IList implementation
T IList<T>.this[int index]
public override void Add(T item)
{
get => ((IReadOnlyList<T>)this)[index];
set => throw new NotSupportedException();
parent.Add(item);
}
object? IList.this[int index]
{
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)
public override bool Contains(T item)
{
return parent.Contains(item);
}
public bool Contains(object? value)
{
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)
public override int IndexOf(T 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
{
ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1);
ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1);
}
}
}