Skip to content

Commit

Permalink
add writableview
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Oct 3, 2024
1 parent 1489313 commit 4cdbe8c
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 19 deletions.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,31 @@ public class WpfDispatcherCollection(Dispatcher dispatcher) : ICollectionEventDi

Views and ToNotifyCollectionChanged are internally connected by events, so they need to be `Dispose` to release those connections.

Standard Views are readonly. If you want to reflect the results of binding back to the original collection, use `CreateWritableView` to generate an `IWritableSynchronizedView`, and then use `ToWritableNotifyCollectionChanged` to create an `INotifyCollectionChanged` collection from it.

```csharp
public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T originalValue, ref bool setValue);

public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
{
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
}
```

`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) =>
{
setValue = true; // or false
return int.Parse(newView);
});
```

Unity
---
In Unity projects, you can installing `ObservableCollections` with [NugetForUnity](https://github.com/GlitchEnzo/NuGetForUnity). If R3 integration is required, similarly install `ObservableCollections.R3` via NuGetForUnity.
Expand Down Expand Up @@ -553,6 +578,37 @@ public static class SynchronizedViewExtensions
}
```

`ObservableList<T>` has writable view.

```csharp
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 delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T originalValue, ref bool setValue);

public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
{
(T Value, TView View) GetAt(int index);
void SetViewAt(int index, TView view);
void SetToSourceCollection(int index, T value);
IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
}

public interface IWritableSynchronizedViewList<TView> : ISynchronizedViewList<TView>
{
new TView this[int index] { get; set; }
}
```

Here are definitions for other collections:

```csharp
Expand Down
27 changes: 17 additions & 10 deletions sandbox/ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,30 @@
using ObservableCollections;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks.Sources;

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) =>
{
setValue = false;
return int.Parse(newView);
});

var dict = new ObservableDictionary<int, string>();
var view = dict.CreateView(x => x);
view.AttachFilter(x => x.Key == 1);
l.Add(0);
l.Add(1);
l.Add(2);
l.Add(3);
l.Add(4);
l.Add(5);

var view2 = view.ToNotifyCollectionChanged();
dict.Add(key: 1, value: "foo");
dict.Add(key: 2, value: "bar");
notify[1] = "99999";

foreach (var item in view2)
foreach (var item in view)
{
Console.WriteLine(item);
}
Console.WriteLine("---");




//var buffer = new ObservableFixedSizeRingBuffer<int>(5);
Expand Down
5 changes: 5 additions & 0 deletions src/ObservableCollections/AlternateIndexList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ public void UpdateAlternateIndex(int startIndex, int incr)
public T this[int index]
{
get => list[index].Value;
set => CollectionsMarshal.AsSpan(list)[index].Value = value;
}

public int GetAlternateIndex(int index) => list[index].AlternateIndex;

public int Count => list.Count;

public int Insert(int alternateIndex, T value)
Expand Down Expand Up @@ -127,6 +130,7 @@ public bool TrySetAtAlternateIndex(int alternateIndex, T value, out int setIndex
return true;
}

/// <summary>NOTE: when replace successfully, list has been sorted.</summary>
public bool TryReplaceAlternateIndex(int getAlternateIndex, int setAlternateIndex)
{
var index = list.BinarySearch(getAlternateIndex);
Expand All @@ -137,6 +141,7 @@ public bool TryReplaceAlternateIndex(int getAlternateIndex, int setAlternateInde

var span = CollectionsMarshal.AsSpan(list);
span[index].AlternateIndex = setAlternateIndex;
list.Sort(); // needs sort to keep order
return true;
}

Expand Down
19 changes: 18 additions & 1 deletion src/ObservableCollections/IObservableCollection.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
Expand All @@ -7,6 +8,7 @@ namespace ObservableCollections
{
public delegate void NotifyCollectionChangedEventHandler<T>(in NotifyCollectionChangedEventArgs<T> e);
public delegate void NotifyViewChangedEventHandler<T, TView>(in SynchronizedViewChangedEventArgs<T, TView> e);
public delegate T WritableViewChangedEventHandler<T, TView>(TView newView, T originalValue, ref bool setValue);

public interface IObservableCollection<T> : IReadOnlyCollection<T>
{
Expand Down Expand Up @@ -49,11 +51,26 @@ public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisp
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
}

public interface IWritableSynchronizedView<T, TView> : ISynchronizedView<T, TView>
{
(T Value, TView View) GetAt(int index);
void SetViewAt(int index, TView view);
void SetToSourceCollection(int index, T value);
IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher);
}

public interface ISynchronizedViewList<out TView> : IReadOnlyList<TView>, IDisposable
{
}

public interface INotifyCollectionChangedSynchronizedViewList<out TView> : ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
public interface IWritableSynchronizedViewList<TView> : ISynchronizedViewList<TView>
{
new TView this[int index] { get; set; }
}

public interface INotifyCollectionChangedSynchronizedViewList<TView> : IList<TView>, IList, ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
{
}

Expand Down
75 changes: 74 additions & 1 deletion src/ObservableCollections/ObservableList.Views.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,36 @@ public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
return new View<TView>(this, transform);
}

internal sealed class View<TView> : ISynchronizedView<T, TView>
public IWritableSynchronizedView<T, TView> CreateWritableView<TView>(Func<T, TView> transform)
{
return new View<TView>(this, transform);
}

public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged()
{
return ToWritableNotifyCollectionChanged(null);
}

public INotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{
return ToWritableNotifyCollectionChanged(static x => x, collectionEventDispatcher, static (T newView, T originalValue, ref bool setValue) =>
{
setValue = true;
return newView;
});
}

public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter)
{
return ToWritableNotifyCollectionChanged(transform, null!);
}

public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher, WritableViewChangedEventHandler<T, TView>? converter)
{
return new NonFilteredNotifyCollectionChangedSynchronizedViewList<T, TView>(CreateView(transform), collectionEventDispatcher, converter);
}

internal sealed class View<TView> : ISynchronizedView<T, TView>, IWritableSynchronizedView<T, TView>
{
public ISynchronizedViewFilter<T> Filter
{
Expand Down Expand Up @@ -301,6 +330,50 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> e)
}
}

#region Writable

public (T Value, TView View) GetAt(int index)
{
lock (SyncRoot)
{
return list[index];
}
}

public void SetViewAt(int index, TView view)
{
lock (SyncRoot)
{
var v = list[index];
list[index] = (v.Item1, view);
}
}

public void SetToSourceCollection(int index, T value)
{
lock (SyncRoot)
{
source[index] = value;
}
}

public IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter)
{
return new FiltableWritableSynchronizedViewList<T, TView>(this, converter);
}

public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter)
{
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null, converter);
}

public INotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher)
{
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null, converter);
}

#endregion

sealed class IgnoreViewComparer : IComparer<(T, TView)>
{
readonly IComparer<T> comparer;
Expand Down
Loading

0 comments on commit 4cdbe8c

Please sign in to comment.