Add R3 ObserveChanged
This commit is contained in:
parent
8b75c41da7
commit
8913192459
30
README.md
30
README.md
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -174,7 +174,7 @@ namespace ObservableCollections
|
||||
}
|
||||
else
|
||||
{
|
||||
ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1);
|
||||
ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user