WritableNotifyCollectionChanged supports Add

This commit is contained in:
neuecc 2024-10-07 18:16:37 +09:00
parent dcfb3edd5a
commit 8afb3fb100
6 changed files with 145 additions and 38 deletions

View File

@ -374,14 +374,59 @@ public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TVie
`ToWritableNotifyCollectionChanged` accepts a delegate called `WritableViewChangedEventHandler`. `newView` receives the newly bound value. If `setValue` is true, it sets a new value to the original collection, triggering notification propagation. The View is also regenerated. If `T originalValue` is a reference type, you can prevent such propagation by setting `setValue` to `false`.
```csharp
var list = new ObservableList<int>();
var view = list.CreateWritableView(x => x.ToString());
view.AttachFilter(x => x % 2 == 0);
IList<string> notify = view.ToWritableNotifyCollectionChanged((string newView, int originalValue, ref bool setValue) =>
var list = new ObservableList<Person>()
{
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<string?> 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

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>

View File

@ -6,30 +6,59 @@ using ObservableCollections;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks.Sources;
using System.Reflection.Emit;
var l = new ObservableList<int>();
var view = l.CreateWritableView(x => x.ToString());
view.AttachFilter(x => x % 2 == 0);
IList<string> notify = view.ToWritableNotifyCollectionChanged((string newView, int originalValue, ref bool setValue) =>
var list = new ObservableList<Person>()
{
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<string?> 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 int.Parse(newView);
return original;
}
else
{
// default setValue == false is Add operation
return new Person { Age = null, Name = newView };
}
});
l.Add(0);
l.Add(1);
l.Add(2);
l.Add(3);
l.Add(4);
l.Add(5);
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<int>(5);

View File

@ -56,6 +56,7 @@ namespace ObservableCollections
(T Value, TView View) GetAt(int index);
void SetViewAt(int index, TView view);
void SetToSourceCollection(int index, T value);
void AddToSourceCollection(T value);
IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);

View File

@ -360,6 +360,14 @@ namespace ObservableCollections
}
}
public void AddToSourceCollection(T value)
{
lock (SyncRoot)
{
source.Add(value);
}
}
public IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter)
{
return new FiltableWritableSynchronizedViewList<T, TView>(this, converter);

View File

@ -557,7 +557,7 @@ internal class FiltableWritableSynchronizedViewList<T, TView> : FiltableSynchron
if (setValue)
{
writableView.SetToSourceCollection(index, newOriginal);
writableView.SetToSourceCollection(originalIndex, newOriginal);
}
}
}
@ -695,7 +695,7 @@ internal class NotifyCollectionChangedSynchronizedViewList<T, TView> :
get => ((IReadOnlyList<TView>)this)[index];
set
{
if (converter == null || parent is not IWritableSynchronizedView<T,TView> writableView)
if (converter == null || parent is not IWritableSynchronizedView<T, TView> writableView)
{
throw new NotSupportedException("This CollectionView does not support set. If base type is ObservableList<T>, you can use ToWritableSynchronizedView and ToWritableNotifyCollectionChanged.");
}
@ -713,7 +713,7 @@ internal class NotifyCollectionChangedSynchronizedViewList<T, TView> :
if (setValue)
{
writableView.SetToSourceCollection(index, newOriginal);
writableView.SetToSourceCollection(originalIndex, newOriginal);
}
}
}
@ -743,12 +743,24 @@ internal class NotifyCollectionChangedSynchronizedViewList<T, TView> :
public void Add(TView item)
{
throw new NotSupportedException();
if (converter == null || parent is not IWritableSynchronizedView<T, TView> writableView)
{
throw new NotSupportedException("This CollectionView does not support Add. If base type is ObservableList<T>, 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 @@ internal class NonFilteredNotifyCollectionChangedSynchronizedViewList<T, TView>
public void Add(TView item)
{
throw new NotSupportedException();
if (converter == null || parent is not IWritableSynchronizedView<T, TView> writableView)
{
throw new NotSupportedException("This CollectionView does not support Add. If base type is ObservableList<T>, 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()