Skip to content

Commit

Permalink
WritableNotifyCollectionChanged supports Add
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Oct 7, 2024
1 parent dcfb3ed commit 8afb3fb
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 38 deletions.
57 changes: 51 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 11 additions & 11 deletions sandbox/ConsoleApp/ConsoleApp.csproj
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ObservableCollections.R3\ObservableCollections.R3.csproj" />
<ProjectReference Include="..\..\src\ObservableCollections\ObservableCollections.csproj" />
<ItemGroup>
<ProjectReference Include="..\..\src\ObservableCollections.R3\ObservableCollections.R3.csproj" />
<ProjectReference Include="..\..\src\ObservableCollections\ObservableCollections.csproj" />
<PackageReference Include="R3" Version="1.0.0" />
</ItemGroup>
</ItemGroup>

</Project>
57 changes: 43 additions & 14 deletions sandbox/ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,59 @@
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>()
{
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<string?> 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<int>(5);

Expand Down
1 change: 1 addition & 0 deletions src/ObservableCollections/IObservableCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TVie
(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);
Expand Down
8 changes: 8 additions & 0 deletions src/ObservableCollections/ObservableList.Views.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,14 @@ public void SetToSourceCollection(int index, T value)
}
}

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);
Expand Down
38 changes: 31 additions & 7 deletions src/ObservableCollections/SynchronizedViewList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ public FiltableWritableSynchronizedViewList(IWritableSynchronizedView<T, TView>

if (setValue)
{
writableView.SetToSourceCollection(index, newOriginal);
writableView.SetToSourceCollection(originalIndex, newOriginal);
}
}
}
Expand Down Expand Up @@ -695,7 +695,7 @@ TView IList<TView>.this[int index]
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.");
}
Expand All @@ -713,7 +713,7 @@ TView IList<TView>.this[int index]

if (setValue)
{
writableView.SetToSourceCollection(index, newOriginal);
writableView.SetToSourceCollection(originalIndex, newOriginal);
}
}
}
Expand Down Expand Up @@ -743,12 +743,24 @@ static bool IsCompatibleObject(object? value)

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()
Expand Down Expand Up @@ -1020,12 +1032,24 @@ static bool IsCompatibleObject(object? value)

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()
Expand Down

0 comments on commit 8afb3fb

Please sign in to comment.