From 8afb3fb100493241c10a883dd02bf5c5f9fd779b Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 7 Oct 2024 18:16:37 +0900 Subject: [PATCH] WritableNotifyCollectionChanged supports Add --- README.md | 57 +++++++++++++++++-- sandbox/ConsoleApp/ConsoleApp.csproj | 22 +++---- sandbox/ConsoleApp/Program.cs | 57 ++++++++++++++----- .../IObservableCollection.cs | 1 + .../ObservableList.Views.cs | 8 +++ .../SynchronizedViewList.cs | 38 ++++++++++--- 6 files changed, 145 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index b12f64f..7da0fc5 100644 --- a/README.md +++ b/README.md @@ -374,14 +374,59 @@ public interface IWritableSynchronizedView : ISynchronizedView(); -var view = list.CreateWritableView(x => x.ToString()); -view.AttachFilter(x => x % 2 == 0); -IList notify = view.ToWritableNotifyCollectionChanged((string newView, int originalValue, ref bool setValue) => +var list = new ObservableList() { - setValue = true; // or false - return int.Parse(newView); + new (){ Age = 10, Name = "John" }, + new (){ Age = 22, Name = "Jeyne" }, + new (){ Age = 30, Name = "Mike" }, +}; +var view = list.CreateWritableView(x => x.Name); +view.AttachFilter(x => x.Age >= 20); + +IList bindable = view.ToWritableNotifyCollectionChanged((string? newView, Person original, ref bool setValue) => +{ + if (setValue) + { + // default setValue == true is Set operation + original.Name = newView; + + // You can modify setValue to false, it does not set original collection to new value. + // For mutable reference types, when there is only a single, + // bound View and to avoid recreating the View, setting false is effective. + // Otherwise, keeping it true will set the value in the original collection as well, + // and change notifications will be sent to lower-level Views(the delegate for View generation will also be called anew). + setValue = false; + return original; + } + else + { + // default setValue == false is Add operation + return new Person { Age = null, Name = newView }; + } }); + +bindable[1] = "Bob"; // change Mike(filtered view's [1]) to Bob. +bindable.Add("Ken"); + +// Show Views +foreach (var item in view) +{ + Console.WriteLine(item); +} + +Console.WriteLine("---"); + +// Show Originals +foreach (var item in list) +{ + Console.WriteLine((item.Age, item.Name)); +} + +public class Person +{ + public int? Age { get; set; } + public string? Name { get; set; } +} ``` Unity diff --git a/sandbox/ConsoleApp/ConsoleApp.csproj b/sandbox/ConsoleApp/ConsoleApp.csproj index a17ef8e..20e29fd 100644 --- a/sandbox/ConsoleApp/ConsoleApp.csproj +++ b/sandbox/ConsoleApp/ConsoleApp.csproj @@ -1,17 +1,17 @@ - - Exe - net6.0 - 10.0 - enable - false - + + Exe + net8.0 + 10.0 + enable + false + - - - + + + - + diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index 5e05fb5..3c099c9 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -6,30 +6,59 @@ using System.Collections; using System.Collections.Generic; using System.Threading.Tasks.Sources; +using System.Reflection.Emit; -var l = new ObservableList(); -var view = l.CreateWritableView(x => x.ToString()); -view.AttachFilter(x => x % 2 == 0); -IList notify = view.ToWritableNotifyCollectionChanged((string newView, int originalValue, ref bool setValue) => +var list = new ObservableList() { - setValue = false; - return int.Parse(newView); -}); + new (){ Age = 10, Name = "John" }, + new (){ Age = 22, Name = "Jeyne" }, + new (){ Age = 30, Name = "Mike" }, +}; +var view = list.CreateWritableView(x => x.Name); +view.AttachFilter(x => x.Age >= 20); + +IList bindable = view.ToWritableNotifyCollectionChanged((string? newView, Person original, ref bool setValue) => +{ + if (setValue) + { + // default setValue == true is Set operation + original.Name = newView; -l.Add(0); -l.Add(1); -l.Add(2); -l.Add(3); -l.Add(4); -l.Add(5); + // You can modify setValue to false, it does not set original collection to new value. + // For mutable reference types, when there is only a single, + // bound View and to avoid recreating the View, setting false is effective. + // Otherwise, keeping it true will set the value in the original collection as well, + // and change notifications will be sent to lower-level Views(the delegate for View generation will also be called anew). + setValue = false; + return original; + } + else + { + // default setValue == false is Add operation + return new Person { Age = null, Name = newView }; + } +}); -notify[1] = "99999"; +// bindable[0] = "takoyaki"; foreach (var item in view) { Console.WriteLine(item); } +Console.WriteLine("---"); + +foreach (var item in list) +{ + Console.WriteLine((item.Age, item.Name)); +} + +public class Person +{ + public int? Age { get; set; } + public string? Name { get; set; } +} + //var buffer = new ObservableFixedSizeRingBuffer(5); diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 226617a..7ff0b71 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -56,6 +56,7 @@ public interface IWritableSynchronizedView : ISynchronizedView ToWritableViewList(WritableViewChangedEventHandler converter); INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter); INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 62dc634..f20f864 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -360,6 +360,14 @@ public void SetToSourceCollection(int index, T value) } } + public void AddToSourceCollection(T value) + { + lock (SyncRoot) + { + source.Add(value); + } + } + public IWritableSynchronizedViewList ToWritableViewList(WritableViewChangedEventHandler converter) { return new FiltableWritableSynchronizedViewList(this, converter); diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index c134034..9287f03 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -557,7 +557,7 @@ public FiltableWritableSynchronizedViewList(IWritableSynchronizedView if (setValue) { - writableView.SetToSourceCollection(index, newOriginal); + writableView.SetToSourceCollection(originalIndex, newOriginal); } } } @@ -695,7 +695,7 @@ TView IList.this[int index] get => ((IReadOnlyList)this)[index]; set { - if (converter == null || parent is not IWritableSynchronizedView writableView) + if (converter == null || parent is not IWritableSynchronizedView writableView) { throw new NotSupportedException("This CollectionView does not support set. If base type is ObservableList, you can use ToWritableSynchronizedView and ToWritableNotifyCollectionChanged."); } @@ -713,7 +713,7 @@ TView IList.this[int index] if (setValue) { - writableView.SetToSourceCollection(index, newOriginal); + writableView.SetToSourceCollection(originalIndex, newOriginal); } } } @@ -743,12 +743,24 @@ static bool IsCompatibleObject(object? value) public void Add(TView item) { - throw new NotSupportedException(); + if (converter == null || parent is not IWritableSynchronizedView writableView) + { + throw new NotSupportedException("This CollectionView does not support Add. If base type is ObservableList, you can use ToWritableSynchronizedView and ToWritableNotifyCollectionChanged."); + } + else + { + var setValue = false; + var newOriginal = converter(item, default!, ref setValue); + + // always add + writableView.AddToSourceCollection(newOriginal); + } } public int Add(object? value) { - throw new NotImplementedException(); + Add((TView)value!); + return -1; // itself does not add in this collection } public void Clear() @@ -1020,12 +1032,24 @@ static bool IsCompatibleObject(object? value) public void Add(TView item) { - throw new NotSupportedException(); + if (converter == null || parent is not IWritableSynchronizedView writableView) + { + throw new NotSupportedException("This CollectionView does not support Add. If base type is ObservableList, you can use ToWritableSynchronizedView and ToWritableNotifyCollectionChanged."); + } + else + { + var setValue = false; + var newOriginal = converter(item, default!, ref setValue); + + // always add + writableView.AddToSourceCollection(newOriginal); + } } public int Add(object? value) { - throw new NotImplementedException(); + Add((TView)value!); + return -1; // itself does not add in this collection } public void Clear()