Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e9cf488b0d | ||
![]() |
57fdc250ce | ||
![]() |
8beb9e1996 | ||
![]() |
5bc5ea17c7 | ||
![]() |
bd6e249304 | ||
![]() |
6f5de33bff | ||
![]() |
7c4974c36a | ||
![]() |
a16edc16ed | ||
![]() |
6e263fa123 | ||
![]() |
b92e0de242 | ||
![]() |
890388ea5e | ||
![]() |
a4d80bda85 | ||
![]() |
43357a5198 | ||
![]() |
c1c9d86ff7 | ||
![]() |
73c5b8e83e | ||
![]() |
bd7cc350f2 | ||
![]() |
b000cc8521 | ||
![]() |
2e678c8559 | ||
![]() |
e7e9145011 | ||
![]() |
62b959c2c2 | ||
![]() |
367be8717c | ||
![]() |
290b455c47 | ||
![]() |
8913192459 | ||
![]() |
8b75c41da7 | ||
![]() |
33fcb7365b | ||
![]() |
7987d75b8d | ||
![]() |
81baa40aab | ||
![]() |
3dc3cb26f9 | ||
![]() |
8afb3fb100 | ||
![]() |
dcfb3edd5a | ||
![]() |
1b3a81ad8d | ||
![]() |
4cdbe8ce34 | ||
![]() |
14893136e5 | ||
![]() |
ee3281ab4a | ||
![]() |
b46163e045 | ||
![]() |
0ce9b3a10e | ||
![]() |
3d26e1d9de | ||
![]() |
9fe7a38b96 | ||
![]() |
9c4f7b2d9e | ||
![]() |
b0bc7c2151 | ||
![]() |
efec73f052 | ||
![]() |
7ad977ffca | ||
![]() |
7a573289ea | ||
![]() |
dce0bb6189 | ||
![]() |
b73a30c366 |
41
.editorconfig
Normal file
41
.editorconfig
Normal file
@ -0,0 +1,41 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Visual Studio Spell checker configs (https://learn.microsoft.com/en-us/visualstudio/ide/text-spell-checker?view=vs-2022#how-to-customize-the-spell-checker)
|
||||
spelling_exclusion_path = ./exclusion.dic
|
||||
|
||||
[*.cs]
|
||||
indent_size = 4
|
||||
charset = utf-8-bom
|
||||
end_of_line = unset
|
||||
|
||||
# Solution files
|
||||
[*.{sln,slnx}]
|
||||
end_of_line = unset
|
||||
|
||||
# MSBuild project files
|
||||
[*.{csproj,props,targets}]
|
||||
end_of_line = unset
|
||||
|
||||
# Xml config files
|
||||
[*.{ruleset,config,nuspec,resx,runsettings,DotSettings}]
|
||||
end_of_line = unset
|
||||
|
||||
[*{_AssemblyInfo.cs,.notsupported.cs}]
|
||||
generated_code = true
|
||||
|
||||
# C# code style settings
|
||||
[*.{cs}]
|
||||
dotnet_diagnostic.IDE0044.severity = none # IDE0044: Make field readonly
|
||||
|
||||
# https://stackoverflow.com/questions/79195382/how-to-disable-fading-unused-methods-in-visual-studio-2022-17-12-0
|
||||
dotnet_diagnostic.IDE0051.severity = none # IDE0051: Remove unused private member
|
||||
dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure
|
5
.github/dependabot.yaml
vendored
5
.github/dependabot.yaml
vendored
@ -5,3 +5,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly" # Check for updates to GitHub Actions every week
|
||||
ignore:
|
||||
# I just want update action when major/minor version is updated. patch updates are too noisy.
|
||||
- dependency-name: '*'
|
||||
update-types:
|
||||
- version-update:semver-patch
|
||||
|
@ -10,10 +10,12 @@ on:
|
||||
|
||||
jobs:
|
||||
build-dotnet:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Cysharp/Actions/.github/actions/checkout@main
|
||||
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main
|
||||
- run: dotnet build -c Debug
|
||||
- run: dotnet test -c Debug --no-build
|
@ -14,10 +14,12 @@ on:
|
||||
|
||||
jobs:
|
||||
build-dotnet:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Cysharp/Actions/.github/actions/checkout@main
|
||||
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main
|
||||
# build and pack
|
||||
- run: dotnet build -c Release -p:Version=${{ inputs.tag }}
|
||||
@ -33,6 +35,8 @@ jobs:
|
||||
# release
|
||||
create-release:
|
||||
needs: [build-dotnet]
|
||||
permissions:
|
||||
contents: write
|
||||
uses: Cysharp/Actions/.github/workflows/create-release.yaml@main
|
||||
with:
|
||||
commit-id: ${{ github.sha }}
|
@ -7,4 +7,6 @@ on:
|
||||
|
||||
jobs:
|
||||
detect:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: Cysharp/Actions/.github/workflows/prevent-github-change.yaml@main
|
@ -7,4 +7,8 @@ on:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main
|
173
README.md
173
README.md
@ -50,15 +50,15 @@ 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()
|
||||
Observable<CollectionMoveEvent<T>> IObservableCollection<T>.ObserveMove()
|
||||
Observable<CollectionResetEvent<T>> IObservableCollection<T>.ObserveReset()
|
||||
Observable<CollectionResetEvent<T>> IObservableCollection<T>.ObserveReset()
|
||||
Observable<Unit> IObservableCollection<T>.ObserveClear<T>()
|
||||
Observable<(int Index, int Count)> IObservableCollection<T>.ObserveReverse<T>()
|
||||
Observable<(int Index, int Count, IComparer<T> Comparer)> IObservableCollection<T>.ObserveSort<T>()
|
||||
Observable<(int Index, int Count, IComparer<T>? Comparer)> IObservableCollection<T>.ObserveSort<T>()
|
||||
Observable<int> IObservableCollection<T>.ObserveCountChanged<T>()
|
||||
```
|
||||
|
||||
@ -237,7 +237,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`, `ObserveCounteChanged` events as Rx, allowing you to compose events individually.
|
||||
|
||||
> dotnet add package [ObservableCollections.R3](https://www.nuget.org/packages/ObservableCollections.R3)
|
||||
|
||||
@ -259,6 +259,8 @@ list.AddRange(new[] { 10, 20, 30 });
|
||||
|
||||
Note that `ObserveReset` is used to subscribe to Clear, Reverse, and Sort operations in bulk.
|
||||
|
||||
In addition to `IObservableCollection<T>`, there is also a subscription event for `ISynchronizedView<T, TView>`. In the case of View, `ObserveRejected` is also added.
|
||||
|
||||
Since it is not supported by dotnet/reactive, please use the Rx library [R3](https://github.com/Cysharp/R3).
|
||||
|
||||
Blazor
|
||||
@ -317,7 +319,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()
|
||||
{
|
||||
@ -355,6 +357,80 @@ public class WpfDispatcherCollection(Dispatcher dispatcher) : ICollectionEventDi
|
||||
}
|
||||
```
|
||||
|
||||
`ToNotifyCollectionChanged()` can also be called without going through a View. In this case, it's guaranteed that no filters will be applied, making it faster. If you want to apply filters, please generate a View before calling it. Additionally, `ObservableList` has a variation called `ToNotifyCollectionChangedSlim()`. This option doesn't generate a list for the View and shares the actual data, making it the fastest and most memory-efficient option. However, range operations such as `AddRange`, `InsertRange` and `RemoveRange` are not supported by WPF (or Avalonia), so they will throw runtime exceptions.
|
||||
|
||||
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>
|
||||
{
|
||||
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
|
||||
NotifyCollectionChangedSynchronizedViewList<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<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 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
|
||||
---
|
||||
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.
|
||||
@ -367,7 +443,7 @@ public class SampleScript : MonoBehaviour
|
||||
public Button prefab;
|
||||
public GameObject root;
|
||||
ObservableRingBuffer<int> collection;
|
||||
ISynchronizedView<GameObject> view;
|
||||
ISynchronizedView<int, GameObject> view;
|
||||
|
||||
void Start()
|
||||
{
|
||||
@ -376,21 +452,25 @@ public class SampleScript : MonoBehaviour
|
||||
{
|
||||
var item = GameObject.Instantiate(prefab);
|
||||
item.GetComponentInChildren<Text>().text = x.ToString();
|
||||
|
||||
// add to root
|
||||
item.transform.SetParent(root.transform);
|
||||
|
||||
return item.gameObject;
|
||||
});
|
||||
view.ViewChanged += View_ViewChanged;
|
||||
}
|
||||
|
||||
void View_ViewChanged(in SynchronizedViewChangedEventArgs<int, string> eventArgs)
|
||||
void View_ViewChanged(in SynchronizedViewChangedEventArgs<int, GameObject> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
eventArgs.NewItem.View.transform.SetParent(root.transform);
|
||||
}
|
||||
else if (NotifyCollectionChangedAction.Remove)
|
||||
// hook remove event
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
GameObject.Destroy(eventArgs.OldItem.View);
|
||||
}
|
||||
|
||||
// hook for Filter attached, clear, etc...
|
||||
// if (NotifyCollectionChangedAction.Reset) { }
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
@ -406,7 +486,7 @@ ObservableCollections provides these collections.
|
||||
|
||||
```csharp
|
||||
class ObservableList<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>, IReadOnlyObservableList<T>
|
||||
class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IObservableCollection<KeyValuePair<TKey, TValue>> where TKey : notnull
|
||||
class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IObservableCollection<KeyValuePair<TKey, TValue>>, IReadOnlyObservableDictionary<TKey, TValue> where TKey : notnull
|
||||
class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T> where T : notnull
|
||||
class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
@ -466,22 +546,28 @@ This is the interface for View:
|
||||
```csharp
|
||||
public delegate void NotifyViewChangedEventHandler<T, TView>(in SynchronizedViewChangedEventArgs<T, TView> e);
|
||||
|
||||
public enum RejectedViewChangedAction
|
||||
{
|
||||
Add, Remove, Move
|
||||
}
|
||||
|
||||
public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisposable
|
||||
{
|
||||
object SyncRoot { get; }
|
||||
ISynchronizedViewFilter<T> Filter { get; }
|
||||
ISynchronizedViewFilter<T, TView> Filter { get; }
|
||||
IEnumerable<(T Value, TView View)> Filtered { get; }
|
||||
IEnumerable<(T Value, TView View)> Unfiltered { get; }
|
||||
int UnfilteredCount { get; }
|
||||
|
||||
event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
|
||||
event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged; // int index, int oldIndex(when RejectedViewChangedAction is Move)
|
||||
event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
void AttachFilter(ISynchronizedViewFilter<T> filter);
|
||||
void AttachFilter(ISynchronizedViewFilter<T, TView> filter);
|
||||
void ResetFilter();
|
||||
ISynchronizedViewList<TView> ToViewList();
|
||||
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
|
||||
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
|
||||
NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
|
||||
NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
|
||||
}
|
||||
```
|
||||
|
||||
@ -526,9 +612,9 @@ When `IsReverse` is true, you need to use `Index` and `Count`. When `IsSort` is
|
||||
For Filter, you can either create one that implements this interface or generate one from a lambda expression using extension methods.
|
||||
|
||||
```csharp
|
||||
public interface ISynchronizedViewFilter<T>
|
||||
public interface ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
bool IsMatch(T value);
|
||||
bool IsMatch(T value, TView view);
|
||||
}
|
||||
|
||||
public static class SynchronizedViewExtensions
|
||||
@ -536,6 +622,46 @@ public static class SynchronizedViewExtensions
|
||||
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, bool> filter)
|
||||
{
|
||||
}
|
||||
|
||||
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, TView, bool> filter)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ObservableList<T>` has writable view.
|
||||
|
||||
```csharp
|
||||
public sealed partial class ObservableList<T>
|
||||
{
|
||||
public IWritableSynchronizedView<T, TView> CreateWritableView<TView>(Func<T, TView> transform);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
void AddToSourceCollection(T value);
|
||||
void InsertIntoSourceCollection(int index, T value);
|
||||
bool RemoveFromSourceCollection(T value);
|
||||
void RemoveAtSourceCollection(int index);
|
||||
void ClearSourceCollection();
|
||||
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; }
|
||||
}
|
||||
```
|
||||
|
||||
@ -556,9 +682,18 @@ public interface ISynchronizedViewList<out TView> : IReadOnlyList<TView>, IDispo
|
||||
{
|
||||
}
|
||||
|
||||
public interface INotifyCollectionChangedSynchronizedViewList<out TView> : ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
// Obsolete for public use
|
||||
public interface INotifyCollectionChangedSynchronizedViewList<TView> : IList<TView>, IList, ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
}
|
||||
|
||||
public abstract class NotifyCollectionChangedSynchronizedViewList<TView> :
|
||||
INotifyCollectionChangedSynchronizedViewList<TView>,
|
||||
IWritableSynchronizedViewList<TView>,
|
||||
IList<TView>,
|
||||
IList
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
License
|
||||
|
BIN
docs/assets.pptx
BIN
docs/assets.pptx
Binary file not shown.
@ -7,7 +7,14 @@
|
||||
Title="AvaloniaApp">
|
||||
<StackPanel>
|
||||
<ListBox ItemsSource="{Binding ItemsView}" x:CompileBindings="False" />
|
||||
<Button Content="Add" Command="{Binding AddCommand}" x:CompileBindings="False" />
|
||||
<Button Content="Clear" Command="{Binding ClearCommand}" x:CompileBindings="False" />
|
||||
<Button Content="Add" Command="{Binding AddCommand}" x:CompileBindings="False"/>
|
||||
<Button Content="AddRange" Command="{Binding AddRangeCommand}" x:CompileBindings="False"/>
|
||||
<Button Content="Insert" Command="{Binding InsertAtRandomCommand}" x:CompileBindings="False" />
|
||||
<Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" x:CompileBindings="False" />
|
||||
<Button Content="Clear" Command="{Binding ClearCommand}" x:CompileBindings="False"/>
|
||||
<Button Content="Reverse" Command="{Binding ReverseCommand}" x:CompileBindings="False" />
|
||||
<Button Content="Sort" Command="{Binding SortCommand}" x:CompileBindings="False"/>
|
||||
<Button Content="AttachFilter" Command="{Binding AttachFilterCommand}" x:CompileBindings="False" />
|
||||
<Button Content="ResetFilter" Command="{Binding ResetFilterCommand}" x:CompileBindings="False" />
|
||||
</StackPanel>
|
||||
</Window>
|
||||
|
@ -26,22 +26,27 @@ namespace AvaloniaApp
|
||||
private ObservableList<int> observableList { get; } = new ObservableList<int>();
|
||||
public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; }
|
||||
public ReactiveCommand<Unit> AddCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> AddRangeCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> InsertAtRandomCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> RemoveAtRandomCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> ClearCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> ReverseCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> SortCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> AttachFilterCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> ResetFilterCommand { get; } = new ReactiveCommand<Unit>();
|
||||
|
||||
public ViewModel()
|
||||
{
|
||||
observableList.Add(1);
|
||||
observableList.Add(2);
|
||||
|
||||
//ItemsView = observableList.CreateView(x => x).ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
|
||||
var view = observableList.CreateView(x => x);
|
||||
ItemsView = view.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
|
||||
|
||||
ItemsView = observableList.CreateView(x => x).ToNotifyCollectionChanged();
|
||||
|
||||
//var test = ItemsView.ToArray();
|
||||
//INotifyCollectionChangedSynchronizedView<int>
|
||||
// ItemsView = observableList.CreateView(x => x).ToNotifyCollectionChanged();
|
||||
// check for optimize list
|
||||
// ItemsView = observableList.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
|
||||
|
||||
// BindingOperations.EnableCollectionSynchronization(ItemsView, new object());
|
||||
|
||||
AddCommand.Subscribe(_ =>
|
||||
{
|
||||
@ -51,12 +56,49 @@ namespace AvaloniaApp
|
||||
});
|
||||
});
|
||||
|
||||
// var iii = 10;
|
||||
AddRangeCommand.Subscribe(_ =>
|
||||
{
|
||||
var xs = Enumerable.Range(1, 5).Select(_ => Random.Shared.Next()).ToArray();
|
||||
observableList.AddRange(xs);
|
||||
});
|
||||
|
||||
InsertAtRandomCommand.Subscribe(_ =>
|
||||
{
|
||||
var from = Random.Shared.Next(0, view.Count);
|
||||
observableList.Insert(from, Random.Shared.Next());
|
||||
});
|
||||
|
||||
RemoveAtRandomCommand.Subscribe(_ =>
|
||||
{
|
||||
var from = Random.Shared.Next(0, view.Count);
|
||||
observableList.RemoveAt(from);
|
||||
});
|
||||
|
||||
ClearCommand.Subscribe(_ =>
|
||||
{
|
||||
// observableList.Add(iii++);
|
||||
observableList.Clear();
|
||||
});
|
||||
|
||||
|
||||
ReverseCommand.Subscribe(_ =>
|
||||
{
|
||||
observableList.Reverse();
|
||||
});
|
||||
|
||||
SortCommand.Subscribe(_ =>
|
||||
{
|
||||
observableList.Sort();
|
||||
});
|
||||
|
||||
AttachFilterCommand.Subscribe(_ =>
|
||||
{
|
||||
view.AttachFilter(x => x % 2 == 0);
|
||||
});
|
||||
|
||||
ResetFilterCommand.Subscribe(_ =>
|
||||
{
|
||||
view.ResetFilter();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -1,75 +1,105 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using R3;
|
||||
using System.Linq;
|
||||
using ObservableCollections;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks.Sources;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
|
||||
|
||||
|
||||
// Queue <-> List Synchronization
|
||||
var queue = new ObservableQueue<int>();
|
||||
|
||||
queue.Enqueue(1);
|
||||
queue.Enqueue(10);
|
||||
queue.Enqueue(100);
|
||||
queue.Enqueue(1000);
|
||||
queue.Enqueue(10000);
|
||||
|
||||
using var view = queue.CreateView(x => x.ToString() + "$");
|
||||
|
||||
using var viewList = view.ToViewList();
|
||||
|
||||
Console.WriteLine(viewList[2]); // 100$
|
||||
|
||||
|
||||
view.ViewChanged += View_ViewChanged;
|
||||
|
||||
void View_ViewChanged(in SynchronizedViewChangedEventArgs<int, string> eventArgs)
|
||||
var list = new ObservableList<Person>()
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
// eventArgs.OldItem.View.
|
||||
}
|
||||
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);
|
||||
|
||||
throw new NotImplementedException();
|
||||
var 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 };
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
list.Clear();
|
||||
|
||||
list.Add(new() { Age = 99, Name = "tako" });
|
||||
|
||||
// bindable[0] = "takoyaki";
|
||||
|
||||
foreach (var item in view)
|
||||
{
|
||||
Console.WriteLine(item);
|
||||
}
|
||||
|
||||
class ViewModel
|
||||
Console.WriteLine("---");
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Value { get; set; } = default!;
|
||||
Console.WriteLine((item.Age, item.Name));
|
||||
}
|
||||
|
||||
class HogeFilter : ISynchronizedViewFilter<int>
|
||||
public class Person
|
||||
{
|
||||
public bool IsMatch(int value)
|
||||
{
|
||||
return value % 2 == 0;
|
||||
}
|
||||
|
||||
public void OnCollectionChanged(in SynchronizedViewChangedEventArgs<int, ViewModel> eventArgs)
|
||||
{
|
||||
switch (eventArgs.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
eventArgs.NewItem.View.Value += " Add";
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
eventArgs.OldItem.View.Value += " Remove";
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
eventArgs.NewItem.View.Value += $" Move {eventArgs.OldStartingIndex} {eventArgs.NewStartingIndex}";
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
eventArgs.NewItem.View.Value += $" Replace {eventArgs.NewStartingIndex}";
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(eventArgs.Action), eventArgs.Action, null);
|
||||
}
|
||||
}
|
||||
public int? Age { get; set; }
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
|
||||
|
||||
//var buffer = new ObservableFixedSizeRingBuffer<int>(5);
|
||||
|
||||
//var view = buffer.CreateView(value => value);
|
||||
//view.AttachFilter(value => value % 2 == 1); // when filtered, mismatch...!
|
||||
|
||||
////{
|
||||
//// INotifyCollectionChangedSynchronizedViewList created from ISynchronizedView with a filter.
|
||||
//var collection = view.ToNotifyCollectionChanged();
|
||||
|
||||
//// Not disposed here.
|
||||
////}
|
||||
|
||||
//buffer.AddFirst(1);
|
||||
//buffer.AddFirst(1);
|
||||
//buffer.AddFirst(2);
|
||||
//buffer.AddFirst(3);
|
||||
//buffer.AddFirst(5);
|
||||
//buffer.AddFirst(8); // Argument out of range
|
||||
//buffer.AddFirst(13);
|
||||
|
||||
//foreach (var item in collection)
|
||||
//{
|
||||
// Console.WriteLine(item);
|
||||
//}
|
||||
|
||||
//Console.WriteLine("---");
|
||||
|
||||
//foreach (var item in view)
|
||||
//{
|
||||
// Console.WriteLine(item);
|
||||
//}
|
||||
|
||||
//Console.WriteLine("---");
|
||||
|
||||
//foreach (var item in buffer)
|
||||
//{
|
||||
// Console.WriteLine(item);
|
||||
//}
|
@ -5,27 +5,62 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WpfApp"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<!--<Grid>
|
||||
Title="MainWindow" Height="800" Width="800">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ListView ItemsSource="{Binding ItemsView}"></ListView>
|
||||
<!-- original -->
|
||||
<StackPanel Grid.Row="0" Grid.ColumnSpan="2" Orientation="Vertical">
|
||||
<ListBox ItemsSource="{Binding ItemsView}" />
|
||||
<Button Content="Add" Command="{Binding AddCommand}" />
|
||||
<Button Content="AddRange" Command="{Binding AddRangeCommand}" />
|
||||
<Button Content="Insert" Command="{Binding InsertAtRandomCommand}" />
|
||||
<Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" />
|
||||
<Button Content="RemoveRange" Command="{Binding RemoveRangeCommand}" />
|
||||
<Button Content="Clear" Command="{Binding ClearCommand}" />
|
||||
<Button Content="Reverse" Command="{Binding ReverseCommand}" />
|
||||
<Button Content="Sort" Command="{Binding SortCommand}" />
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Column="1" Click="Button_Click">Insert</Button>
|
||||
<!-- Upper left (NotWritable, NonFilter) -->
|
||||
<GroupBox Grid.Row="1" Grid.Column="0" Header="NotWritable, NonFilter">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<DataGrid ItemsSource="{Binding NotWritableNonFilterView}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
</Grid>-->
|
||||
<StackPanel>
|
||||
<ListBox ItemsSource="{Binding ItemsView}" />
|
||||
<Button Content="Add" Command="{Binding AddCommand}" />
|
||||
<Button Content="Insert" Command="{Binding InsertAtRandomCommand}" />
|
||||
<Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" />
|
||||
<Button Content="Clear" Command="{Binding ClearCommand}" />
|
||||
<Button Content="Reverse" Command="{Binding ReverseCommand}" />
|
||||
<Button Content="Sort" Command="{Binding SortCommand}" />
|
||||
<Button Content="AttachFilter" Command="{Binding AttachFilterCommand}" />
|
||||
<Button Content="ResetFilter" Command="{Binding ResetFilterCommand}" />
|
||||
</StackPanel>
|
||||
<!-- Upper right (Writable, NonFilter) -->
|
||||
<GroupBox Grid.Row="1" Grid.Column="1" Header="Writable, NonFilter">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<DataGrid ItemsSource="{Binding WritableNonFilterPersonView}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- Lower left (NotWritable, Filter) -->
|
||||
<GroupBox Grid.Row="2" Grid.Column="0" Header="NotWritable, Filter">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<DataGrid ItemsSource="{Binding NotWritableFilterView}" />
|
||||
<Button Content="AttachFilter" Command="{Binding AttachFilterCommand2}" />
|
||||
<Button Content="ResetFilter" Command="{Binding ResetFilterCommand2}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- Lower right (Writable, Filter) -->
|
||||
<GroupBox Grid.Row="2" Grid.Column="1" Header="Writable, Filter">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<DataGrid ItemsSource="{Binding WritableFilterPersonView}" />
|
||||
<Button Content="AttachFilter" Command="{Binding AttachFilterCommand3}" />
|
||||
<Button Content="ResetFilter" Command="{Binding ResetFilterCommand3}" />
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
@ -2,6 +2,7 @@
|
||||
using R3;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@ -76,22 +77,36 @@ namespace WpfApp
|
||||
private ObservableList<int> observableList { get; } = new ObservableList<int>();
|
||||
public INotifyCollectionChangedSynchronizedViewList<int> ItemsView { get; }
|
||||
public ReactiveCommand<Unit> AddCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> AddRangeCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> InsertAtRandomCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> RemoveAtRandomCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> RemoveRangeCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> ClearCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> ReverseCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> SortCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> AttachFilterCommand { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> ResetFilterCommand { get; } = new ReactiveCommand<Unit>();
|
||||
|
||||
private ObservableList<Person> SourceList { get; } = [];
|
||||
private IWritableSynchronizedView<Person, Person> writableFilter;
|
||||
private ISynchronizedView<Person, Person> notWritableFilter;
|
||||
public NotifyCollectionChangedSynchronizedViewList<Person> NotWritableNonFilterView { get; }
|
||||
public NotifyCollectionChangedSynchronizedViewList<Person> NotWritableFilterView { get; }
|
||||
public NotifyCollectionChangedSynchronizedViewList<Person> WritableNonFilterPersonView { get; }
|
||||
public NotifyCollectionChangedSynchronizedViewList<Person> WritableFilterPersonView { get; }
|
||||
public ReactiveCommand<Unit> AttachFilterCommand2 { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> ResetFilterCommand2 { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> AttachFilterCommand3 { get; } = new ReactiveCommand<Unit>();
|
||||
public ReactiveCommand<Unit> ResetFilterCommand3 { get; } = new ReactiveCommand<Unit>();
|
||||
|
||||
public ViewModel()
|
||||
{
|
||||
observableList.Add(1);
|
||||
observableList.Add(2);
|
||||
|
||||
var view = observableList.CreateView(x => x);
|
||||
ItemsView = view.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
|
||||
|
||||
//ItemsView = view.ToNotifyCollectionChanged();
|
||||
ItemsView = observableList.ToNotifyCollectionChanged();
|
||||
|
||||
// check for optimize list
|
||||
// ItemsView = observableList.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
|
||||
@ -99,10 +114,16 @@ namespace WpfApp
|
||||
|
||||
AddCommand.Subscribe(_ =>
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
// ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
observableList.Add(Random.Shared.Next());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AddRangeCommand.Subscribe(_ =>
|
||||
{
|
||||
var xs = Enumerable.Range(1, 5).Select(_ => Random.Shared.Next()).ToArray();
|
||||
observableList.AddRange(xs);
|
||||
});
|
||||
|
||||
InsertAtRandomCommand.Subscribe(_ =>
|
||||
@ -117,6 +138,11 @@ namespace WpfApp
|
||||
observableList.RemoveAt(from);
|
||||
});
|
||||
|
||||
RemoveRangeCommand.Subscribe(_ =>
|
||||
{
|
||||
observableList.RemoveRange(2, 5);
|
||||
});
|
||||
|
||||
ClearCommand.Subscribe(_ =>
|
||||
{
|
||||
observableList.Clear();
|
||||
@ -142,8 +168,97 @@ namespace WpfApp
|
||||
{
|
||||
view.ResetFilter();
|
||||
});
|
||||
|
||||
SourceList.Add(new() { Name = "a", Age = 1 });
|
||||
SourceList.Add(new() { Name = "b", Age = 2 });
|
||||
SourceList.Add(new() { Name = "c", Age = 3 });
|
||||
SourceList.Add(new() { Name = "d", Age = 4 });
|
||||
//NotWritable, NonFilter
|
||||
NotWritableNonFilterView = SourceList.ToNotifyCollectionChanged();
|
||||
|
||||
//NotWritable, Filter
|
||||
notWritableFilter = SourceList.CreateView(x => x);
|
||||
NotWritableFilterView = notWritableFilter.ToNotifyCollectionChanged();
|
||||
|
||||
//Writable, NonFilter
|
||||
WritableNonFilterPersonView = SourceList.ToWritableNotifyCollectionChanged();
|
||||
|
||||
//WritableNonFilterPersonView = SourceList.ToWritableNotifyCollectionChanged(x => x, (Person newView, Person original, ref bool setValue) =>
|
||||
//{
|
||||
// if (setValue)
|
||||
// {
|
||||
// // default setValue == true is Set operation
|
||||
// original.Name = newView.Name;
|
||||
// original.Age = newView.Age;
|
||||
|
||||
// // 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 = newView.Age, Name = newView.Name };
|
||||
// }
|
||||
//}, null);
|
||||
|
||||
//Writable, Filter
|
||||
writableFilter = SourceList.CreateWritableView(x => x);
|
||||
WritableFilterPersonView = writableFilter.ToWritableNotifyCollectionChanged();
|
||||
|
||||
//WritableFilterPersonView = writableFilter.ToWritableNotifyCollectionChanged((Person newView, Person original, ref bool setValue) =>
|
||||
//{
|
||||
// if (setValue)
|
||||
// {
|
||||
// // default setValue == true is Set operation
|
||||
// original.Name = newView.Name;
|
||||
// original.Age = newView.Age;
|
||||
|
||||
// // 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 = newView.Age, Name = newView.Name };
|
||||
// }
|
||||
//});
|
||||
|
||||
AttachFilterCommand2.Subscribe(_ =>
|
||||
{
|
||||
notWritableFilter.AttachFilter(x => x.Age % 2 == 0);
|
||||
});
|
||||
|
||||
ResetFilterCommand2.Subscribe(_ =>
|
||||
{
|
||||
notWritableFilter.ResetFilter();
|
||||
});
|
||||
|
||||
AttachFilterCommand3.Subscribe(_ =>
|
||||
{
|
||||
writableFilter.AttachFilter(x => x.Age % 2 == 0);
|
||||
});
|
||||
|
||||
ResetFilterCommand3.Subscribe(_ =>
|
||||
{
|
||||
writableFilter.ResetFilter();
|
||||
});
|
||||
}
|
||||
}
|
||||
public class Person
|
||||
{
|
||||
public int? Age { get; set; }
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
|
||||
public class WpfDispatcherCollection(Dispatcher dispatcher) : ICollectionEventDispatcher
|
||||
{
|
||||
|
@ -0,0 +1,509 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using R3;
|
||||
|
||||
namespace ObservableCollections;
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly record struct ViewChangedEvent<T, TView>
|
||||
{
|
||||
public readonly NotifyCollectionChangedAction Action;
|
||||
public readonly (T Value, TView View) NewItem;
|
||||
public readonly (T Value, TView View) OldItem;
|
||||
public readonly int NewStartingIndex;
|
||||
public readonly int OldStartingIndex;
|
||||
public readonly SortOperation<T> SortOperation;
|
||||
|
||||
public ViewChangedEvent(NotifyCollectionChangedAction action, (T, TView) newItem, (T, TView) oldItem, int newStartingIndex, int oldStartingIndex, SortOperation<T> sortOperation)
|
||||
{
|
||||
Action = action;
|
||||
NewItem = newItem;
|
||||
OldItem = oldItem;
|
||||
NewStartingIndex = newStartingIndex;
|
||||
OldStartingIndex = oldStartingIndex;
|
||||
SortOperation = sortOperation;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly record struct RejectedViewChangedEvent
|
||||
{
|
||||
public readonly RejectedViewChangedAction Action;
|
||||
public readonly int NewIndex;
|
||||
public readonly int OldIndex;
|
||||
|
||||
public RejectedViewChangedEvent(RejectedViewChangedAction action, int newIndex, int oldIndex)
|
||||
{
|
||||
Action = action;
|
||||
NewIndex = newIndex;
|
||||
OldIndex = oldIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class ObservableCollectionR3Extensions
|
||||
{
|
||||
public static Observable<RejectedViewChangedEvent> ObserveRejected<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewRejected<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<ViewChangedEvent<T, TView>> ObserveChanged<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewChanged<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<CollectionAddEvent<(T Value, TView View)>> ObserveAdd<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewAdd<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<CollectionRemoveEvent<(T Value, TView View)>> ObserveRemove<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewRemove<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<CollectionReplaceEvent<(T Value, TView View)>> ObserveReplace<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewReplace<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<CollectionMoveEvent<(T Value, TView View)>> ObserveMove<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewMove<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<CollectionResetEvent<T>> ObserveReset<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewReset<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<Unit> ObserveClear<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewClear<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<(int Index, int Count)> ObserveReverse<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewReverse<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<(int Index, int Count, IComparer<T>? Comparer)> ObserveSort<T, TView>(this ISynchronizedView<T, TView> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewSort<T, TView>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<int> ObserveCountChanged<T, TView>(this ISynchronizedView<T, TView> source, bool notifyCurrentCount = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new SynchronizedViewCountChanged<T, TView>(source, notifyCurrentCount, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewChanged<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<ViewChangedEvent<T, TView>>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<ViewChangedEvent<T, TView>> observer)
|
||||
{
|
||||
return new _SynchronizedViewChanged(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewChanged(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<ViewChangedEvent<T, TView>> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, ViewChangedEvent<T, TView>>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.IsSingleItem)
|
||||
{
|
||||
var newArgs = new ViewChangedEvent<T, TView>(
|
||||
eventArgs.Action,
|
||||
eventArgs.NewItem,
|
||||
eventArgs.OldItem,
|
||||
eventArgs.NewStartingIndex,
|
||||
eventArgs.OldStartingIndex,
|
||||
eventArgs.SortOperation);
|
||||
|
||||
observer.OnNext(newArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
var index = eventArgs.NewStartingIndex;
|
||||
for (int i = 0; i < eventArgs.NewValues.Length; i++)
|
||||
{
|
||||
var newItem = (eventArgs.NewValues[i], eventArgs.NewViews[i]);
|
||||
var newArgs = new ViewChangedEvent<T, TView>(
|
||||
eventArgs.Action,
|
||||
newItem,
|
||||
default,
|
||||
index++,
|
||||
eventArgs.OldStartingIndex,
|
||||
eventArgs.SortOperation);
|
||||
|
||||
observer.OnNext(newArgs);
|
||||
}
|
||||
}
|
||||
else if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
|
||||
for (int i = 0; i < eventArgs.OldValues.Length; i++)
|
||||
{
|
||||
var oldItem = (eventArgs.OldValues[i], eventArgs.OldViews[i]);
|
||||
var newArgs = new ViewChangedEvent<T, TView>(
|
||||
eventArgs.Action,
|
||||
default,
|
||||
oldItem,
|
||||
eventArgs.NewStartingIndex,
|
||||
eventArgs.OldStartingIndex, // removed, uses same index
|
||||
eventArgs.SortOperation);
|
||||
|
||||
observer.OnNext(newArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewAdd<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<CollectionAddEvent<(T, TView)>>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<CollectionAddEvent<(T, TView)>> observer)
|
||||
{
|
||||
return new _SynchronizedViewAdd(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewAdd(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<CollectionAddEvent<(T, TView)>> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, CollectionAddEvent<(T, TView)>>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
if (eventArgs.IsSingleItem)
|
||||
{
|
||||
observer.OnNext(new CollectionAddEvent<(T, TView)>(eventArgs.NewStartingIndex, eventArgs.NewItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
var index = eventArgs.NewStartingIndex;
|
||||
for (int i = 0; i < eventArgs.NewValues.Length; i++)
|
||||
{
|
||||
observer.OnNext(new CollectionAddEvent<(T, TView)>(index++, (eventArgs.NewValues[i], eventArgs.NewViews[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewRemove<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<CollectionRemoveEvent<(T, TView)>>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<CollectionRemoveEvent<(T, TView)>> observer)
|
||||
{
|
||||
return new _SynchronizedViewRemove(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewRemove(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<CollectionRemoveEvent<(T, TView)>> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, CollectionRemoveEvent<(T, TView)>>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
if (eventArgs.IsSingleItem)
|
||||
{
|
||||
observer.OnNext(new CollectionRemoveEvent<(T, TView)>(eventArgs.OldStartingIndex, eventArgs.OldItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < eventArgs.OldValues.Length; i++)
|
||||
{
|
||||
observer.OnNext(new CollectionRemoveEvent<(T, TView)>(eventArgs.OldStartingIndex, (eventArgs.OldValues[i], eventArgs.OldViews[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewReplace<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<CollectionReplaceEvent<(T, TView)>>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<CollectionReplaceEvent<(T, TView)>> observer)
|
||||
{
|
||||
return new _SynchronizedViewReplace(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewReplace(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<CollectionReplaceEvent<(T, TView)>> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, CollectionReplaceEvent<(T, TView)>>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Replace)
|
||||
{
|
||||
observer.OnNext(new CollectionReplaceEvent<(T, TView)>(eventArgs.NewStartingIndex, eventArgs.OldItem, eventArgs.NewItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewMove<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<CollectionMoveEvent<(T, TView)>>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<CollectionMoveEvent<(T, TView)>> observer)
|
||||
{
|
||||
return new _SynchronizedViewMove(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewMove(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<CollectionMoveEvent<(T, TView)>> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, CollectionMoveEvent<(T, TView)>>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Move)
|
||||
{
|
||||
observer.OnNext(new CollectionMoveEvent<(T, TView)>(eventArgs.OldStartingIndex, eventArgs.NewStartingIndex, eventArgs.NewItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewReset<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<CollectionResetEvent<T>>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<CollectionResetEvent<T>> observer)
|
||||
{
|
||||
return new _SynchronizedViewReset(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewReset(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<CollectionResetEvent<T>> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, CollectionResetEvent<T>>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
observer.OnNext(new CollectionResetEvent<T>(eventArgs.SortOperation));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewClear<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<Unit>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<Unit> observer)
|
||||
{
|
||||
return new _SynchronizedViewClear(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewClear(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<Unit> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, Unit>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsClear)
|
||||
{
|
||||
observer.OnNext(Unit.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewReverse<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<(int Index, int Count)>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<(int Index, int Count)> observer)
|
||||
{
|
||||
return new _SynchronizedViewReverse(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewReverse(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<(int Index, int Count)> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, (int Index, int Count)>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsReverse)
|
||||
{
|
||||
observer.OnNext((eventArgs.SortOperation.Index, eventArgs.SortOperation.Count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewSort<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<(int Index, int Count, IComparer<T>? Comparer)>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<(int Index, int Count, IComparer<T>? Comparer)> observer)
|
||||
{
|
||||
return new _SynchronizedViewSort(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewSort(
|
||||
ISynchronizedView<T, TView> source,
|
||||
Observer<(int Index, int Count, IComparer<T>? Comparer)> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: SynchronizedViewObserverBase<T, TView, (int Index, int Count, IComparer<T>? Comparer)>(source, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsSort)
|
||||
{
|
||||
observer.OnNext(eventArgs.SortOperation.AsTuple());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SynchronizedViewCountChanged<T, TView>(ISynchronizedView<T, TView> source, bool notifyCurrentCount, CancellationToken cancellationToken)
|
||||
: Observable<int>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<int> observer)
|
||||
{
|
||||
return new _SynchronizedViewCountChanged(source, notifyCurrentCount, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewCountChanged : SynchronizedViewObserverBase<T, TView, int>
|
||||
{
|
||||
int countPrev;
|
||||
|
||||
public _SynchronizedViewCountChanged(
|
||||
ISynchronizedView<T, TView> source,
|
||||
bool notifyCurrentCount,
|
||||
Observer<int> observer,
|
||||
CancellationToken cancellationToken) : base(source, observer, cancellationToken)
|
||||
{
|
||||
this.countPrev = source.Count;
|
||||
if (notifyCurrentCount)
|
||||
{
|
||||
observer.OnNext(source.Count);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs)
|
||||
{
|
||||
switch (eventArgs.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
case NotifyCollectionChangedAction.Reset when countPrev != source.Count:
|
||||
observer.OnNext(source.Count);
|
||||
break;
|
||||
}
|
||||
countPrev = source.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
sealed class SynchronizedViewRejected<T, TView>(ISynchronizedView<T, TView> source, CancellationToken cancellationToken)
|
||||
: Observable<RejectedViewChangedEvent>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<RejectedViewChangedEvent> observer)
|
||||
{
|
||||
return new _SynchronizedViewRejected(source, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _SynchronizedViewRejected : IDisposable
|
||||
{
|
||||
readonly ISynchronizedView<T, TView> source;
|
||||
readonly Observer<RejectedViewChangedEvent> observer;
|
||||
readonly CancellationTokenRegistration cancellationTokenRegistration;
|
||||
readonly Action<RejectedViewChangedAction, int, int> handlerDelegate;
|
||||
|
||||
public _SynchronizedViewRejected(ISynchronizedView<T, TView> source, Observer<RejectedViewChangedEvent> observer, CancellationToken cancellationToken)
|
||||
{
|
||||
this.source = source;
|
||||
this.observer = observer;
|
||||
this.handlerDelegate = Handler;
|
||||
|
||||
source.RejectedViewChanged += handlerDelegate;
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
cancellationTokenRegistration = cancellationToken.UnsafeRegister(static state =>
|
||||
{
|
||||
var s = (_SynchronizedViewRejected)state!;
|
||||
s.observer.OnCompleted();
|
||||
s.Dispose();
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
source.RejectedViewChanged -= handlerDelegate;
|
||||
cancellationTokenRegistration.Dispose();
|
||||
}
|
||||
|
||||
void Handler(RejectedViewChangedAction rejectedViewChangedAction, int newIndex, int oldIndex)
|
||||
{
|
||||
observer.OnNext(new RejectedViewChangedEvent(rejectedViewChangedAction, newIndex, oldIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SynchronizedViewObserverBase<T, TView, TEvent> : IDisposable
|
||||
{
|
||||
protected readonly ISynchronizedView<T, TView> source;
|
||||
protected readonly Observer<TEvent> observer;
|
||||
readonly CancellationTokenRegistration cancellationTokenRegistration;
|
||||
readonly NotifyViewChangedEventHandler<T, TView> handlerDelegate;
|
||||
|
||||
public SynchronizedViewObserverBase(ISynchronizedView<T, TView> source, Observer<TEvent> observer, CancellationToken cancellationToken)
|
||||
{
|
||||
this.source = source;
|
||||
this.observer = observer;
|
||||
this.handlerDelegate = Handler;
|
||||
|
||||
source.ViewChanged += handlerDelegate;
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
cancellationTokenRegistration = cancellationToken.UnsafeRegister(static state =>
|
||||
{
|
||||
var s = (SynchronizedViewObserverBase<T, TView, TEvent>)state!;
|
||||
s.observer.OnCompleted();
|
||||
s.Dispose();
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
source.ViewChanged -= handlerDelegate;
|
||||
cancellationTokenRegistration.Dispose();
|
||||
}
|
||||
|
||||
protected abstract void Handler(in SynchronizedViewChangedEventArgs<T, TView> eventArgs);
|
||||
}
|
@ -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 partial 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);
|
||||
@ -71,7 +97,7 @@ public static class ObservableCollectionR3Extensions
|
||||
return new ObservableCollectionReverse<T>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static Observable<(int Index, int Count, IComparer<T> Comparer)> ObserveSort<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
|
||||
public static Observable<(int Index, int Count, IComparer<T>? Comparer)> ObserveSort<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new ObservableCollectionSort<T>(source, cancellationToken);
|
||||
}
|
||||
@ -102,6 +128,74 @@ 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,
|
||||
item,
|
||||
eventArgs.OldItem,
|
||||
newStartingIndex: i,
|
||||
eventArgs.OldStartingIndex,
|
||||
eventArgs.SortOperation);
|
||||
|
||||
if (eventArgs.NewStartingIndex != -1) i++;
|
||||
|
||||
observer.OnNext(newArgs);
|
||||
}
|
||||
}
|
||||
else if (eventArgs.Action == NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
foreach (var item in eventArgs.OldItems)
|
||||
{
|
||||
var newArgs = new CollectionChangedEvent<T>(
|
||||
eventArgs.Action,
|
||||
eventArgs.NewItem,
|
||||
item,
|
||||
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 +255,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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,18 +383,18 @@ sealed class ObservableCollectionReverse<T>(IObservableCollection<T> collection,
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ObservableCollectionSort<T>(IObservableCollection<T> collection, CancellationToken cancellationToken) : Observable<(int Index, int Count, IComparer<T> Comparer)>
|
||||
sealed class ObservableCollectionSort<T>(IObservableCollection<T> collection, CancellationToken cancellationToken) : Observable<(int Index, int Count, IComparer<T>? Comparer)>
|
||||
{
|
||||
protected override IDisposable SubscribeCore(Observer<(int Index, int Count, IComparer<T> Comparer)> observer)
|
||||
protected override IDisposable SubscribeCore(Observer<(int Index, int Count, IComparer<T>? Comparer)> observer)
|
||||
{
|
||||
return new _ObservableCollectionSort(collection, observer, cancellationToken);
|
||||
}
|
||||
|
||||
sealed class _ObservableCollectionSort(
|
||||
IObservableCollection<T> collection,
|
||||
Observer<(int Index, int Count, IComparer<T> Comparer)> observer,
|
||||
Observer<(int Index, int Count, IComparer<T>? Comparer)> observer,
|
||||
CancellationToken cancellationToken)
|
||||
: ObservableCollectionObserverBase<T, (int Index, int Count, IComparer<T> Comparer)>(collection, observer, cancellationToken)
|
||||
: ObservableCollectionObserverBase<T, (int Index, int Count, IComparer<T>? Comparer)>(collection, observer, cancellationToken)
|
||||
{
|
||||
protected override void Handler(in NotifyCollectionChangedEventArgs<T> eventArgs)
|
||||
{
|
||||
|
@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
@ -24,7 +23,7 @@ public class AlternateIndexList<T> : IEnumerable<T>
|
||||
this.list = values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)).ToList();
|
||||
}
|
||||
|
||||
void UpdateAlternateIndex(int startIndex, int incr)
|
||||
public void UpdateAlternateIndex(int startIndex, int incr)
|
||||
{
|
||||
var span = CollectionsMarshal.AsSpan(list);
|
||||
for (int i = startIndex; i < span.Length; i++)
|
||||
@ -36,8 +35,11 @@ public class AlternateIndexList<T> : IEnumerable<T>
|
||||
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)
|
||||
@ -80,11 +82,15 @@ public class AlternateIndexList<T> : IEnumerable<T>
|
||||
public int RemoveAt(int alternateIndex)
|
||||
{
|
||||
var index = list.BinarySearch(alternateIndex);
|
||||
if (index != -1)
|
||||
if (index >= 0)
|
||||
{
|
||||
list.RemoveAt(index);
|
||||
UpdateAlternateIndex(index, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Index was not found. AlternateIndex:" + alternateIndex);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
@ -124,6 +130,21 @@ public class AlternateIndexList<T> : IEnumerable<T>
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>NOTE: when replace successfully, list has been sorted.</summary>
|
||||
public bool TryReplaceAlternateIndex(int getAlternateIndex, int setAlternateIndex)
|
||||
{
|
||||
var index = list.BinarySearch(getAlternateIndex);
|
||||
if (index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var span = CollectionsMarshal.AsSpan(list);
|
||||
span[index].AlternateIndex = setAlternateIndex;
|
||||
list.Sort(); // needs sort to keep order
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReplaceByValue(T searchValue, T replaceValue, out int replacedIndex)
|
||||
{
|
||||
replacedIndex = list.FindIndex(x => EqualityComparer<T>.Default.Equals(x.Value, searchValue));
|
||||
@ -178,7 +199,7 @@ public class AlternateIndexList<T> : IEnumerable<T>
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose() => iter.Reset();
|
||||
public void Dispose() => iter.Dispose();
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
@ -191,7 +212,7 @@ public class AlternateIndexList<T> : IEnumerable<T>
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset() => iter.Reset();
|
||||
public void Reset() { }
|
||||
|
||||
public IEnumerator<IndexedValue> GetEnumerator() => this;
|
||||
|
||||
|
@ -35,7 +35,16 @@ namespace ObservableCollections
|
||||
|
||||
public void Post(CollectionEventDispatcherEventArgs ev)
|
||||
{
|
||||
synchronizationContext.Post(callback, ev);
|
||||
if (SynchronizationContext.Current == null)
|
||||
{
|
||||
// non-UI thread, post the event asynchronously
|
||||
synchronizationContext.Post(callback, ev);
|
||||
}
|
||||
else
|
||||
{
|
||||
// UI thread, send the event synchronously
|
||||
synchronizationContext.Send(callback, ev);
|
||||
}
|
||||
}
|
||||
|
||||
static void SendOrPostCallback(object? state)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
@ -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>
|
||||
{
|
||||
@ -25,30 +27,176 @@ namespace ObservableCollections
|
||||
{
|
||||
}
|
||||
|
||||
public enum RejectedViewChangedAction
|
||||
{
|
||||
Add, Remove, Move
|
||||
}
|
||||
|
||||
public interface ISynchronizedView<T, TView> : IReadOnlyCollection<TView>, IDisposable
|
||||
{
|
||||
object SyncRoot { get; }
|
||||
ISynchronizedViewFilter<T> Filter { get; }
|
||||
ISynchronizedViewFilter<T, TView> Filter { get; }
|
||||
IEnumerable<(T Value, TView View)> Filtered { get; }
|
||||
IEnumerable<(T Value, TView View)> Unfiltered { get; }
|
||||
int UnfilteredCount { get; }
|
||||
|
||||
event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
|
||||
event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
|
||||
event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
void AttachFilter(ISynchronizedViewFilter<T> filter);
|
||||
void AttachFilter(ISynchronizedViewFilter<T, TView> filter);
|
||||
void ResetFilter();
|
||||
ISynchronizedViewList<TView> ToViewList();
|
||||
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
|
||||
INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
|
||||
NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged();
|
||||
NotifyCollectionChangedSynchronizedViewList<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);
|
||||
void AddToSourceCollection(T value);
|
||||
void InsertIntoSourceCollection(int index, T value);
|
||||
bool RemoveFromSourceCollection(T value);
|
||||
void RemoveAtSourceCollection(int index);
|
||||
void ClearSourceCollection();
|
||||
IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter);
|
||||
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged();
|
||||
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter);
|
||||
NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher);
|
||||
NotifyCollectionChangedSynchronizedViewList<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; }
|
||||
}
|
||||
|
||||
// only for compatibility, use NotifyCollectionChangedSynchronizedViewList insetad.
|
||||
// [Obsolete] in future
|
||||
public interface INotifyCollectionChangedSynchronizedViewList<TView> : IList<TView>, IList, ISynchronizedViewList<TView>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
}
|
||||
|
||||
// IColleciton<T>.Count and ICollection.Count will be ambigious so use abstract class instead of interface
|
||||
public abstract class NotifyCollectionChangedSynchronizedViewList<TView> :
|
||||
INotifyCollectionChangedSynchronizedViewList<TView>,
|
||||
IWritableSynchronizedViewList<TView>,
|
||||
IList<TView>,
|
||||
IList
|
||||
{
|
||||
protected readonly object gate = new object();
|
||||
|
||||
public abstract TView this[int index] { get; set; }
|
||||
|
||||
object? IList.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return this[index];
|
||||
}
|
||||
set => ((IList<TView>)this)[index] = (TView)value!;
|
||||
}
|
||||
|
||||
public abstract int Count { get; }
|
||||
public virtual bool IsReadOnly { get; } = true;
|
||||
public bool IsFixedSize => IsReadOnly;
|
||||
public bool IsSynchronized => true;
|
||||
public object SyncRoot => gate;
|
||||
|
||||
public abstract event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||
public abstract event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public abstract void Add(TView item);
|
||||
|
||||
int IList.Add(object? value)
|
||||
{
|
||||
Add((TView)value!);
|
||||
return Count - 1;
|
||||
}
|
||||
|
||||
public abstract void Insert(int index, TView item);
|
||||
public abstract bool Remove(TView item);
|
||||
public abstract void RemoveAt(int index);
|
||||
public abstract void Clear();
|
||||
|
||||
public abstract bool Contains(TView item);
|
||||
|
||||
bool IList.Contains(object? value)
|
||||
{
|
||||
if (IsCompatibleObject(value))
|
||||
{
|
||||
return Contains((TView)value!);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void Dispose();
|
||||
public abstract IEnumerator<TView> GetEnumerator();
|
||||
public abstract int IndexOf(TView item);
|
||||
|
||||
int IList.IndexOf(object? item)
|
||||
{
|
||||
if (IsCompatibleObject(item))
|
||||
{
|
||||
return IndexOf((TView)item!);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
static bool IsCompatibleObject(object? value)
|
||||
{
|
||||
return value is TView || value == null && default(TView) == null;
|
||||
}
|
||||
|
||||
void ICollection<TView>.Clear()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
void IList.Clear()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void ICollection<TView>.CopyTo(TView[] array, int arrayIndex) => throw new NotSupportedException();
|
||||
void ICollection.CopyTo(Array array, int index) => throw new NotSupportedException();
|
||||
|
||||
void IList<TView>.Insert(int index, TView item)
|
||||
{
|
||||
Insert(index, item);
|
||||
}
|
||||
|
||||
void IList.Insert(int index, object? value)
|
||||
{
|
||||
Insert(index, (TView)value!);
|
||||
}
|
||||
|
||||
bool ICollection<TView>.Remove(TView item)
|
||||
{
|
||||
return Remove(item!);
|
||||
}
|
||||
|
||||
void IList.Remove(object? value)
|
||||
{
|
||||
Remove((TView)value!);
|
||||
}
|
||||
|
||||
void IList.RemoveAt(int index)
|
||||
{
|
||||
RemoveAt(index);
|
||||
}
|
||||
|
||||
void IList<TView>.RemoveAt(int index) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public static class ObservableCollectionExtensions
|
||||
@ -61,28 +209,28 @@ namespace ObservableCollections
|
||||
public static ISynchronizedViewList<TView> ToViewList<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform)
|
||||
{
|
||||
// Optimized for non filtered
|
||||
return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform));
|
||||
return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform), isSupportRangeFeature: true, null, null);
|
||||
}
|
||||
|
||||
public static INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged<T>(this IObservableCollection<T> collection)
|
||||
public static NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged<T>(this IObservableCollection<T> collection)
|
||||
{
|
||||
return ToNotifyCollectionChanged(collection, null);
|
||||
}
|
||||
|
||||
public static INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged<T>(this IObservableCollection<T> collection, ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
public static NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged<T>(this IObservableCollection<T> collection, ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return ToNotifyCollectionChanged(collection, static x => x, collectionEventDispatcher);
|
||||
}
|
||||
|
||||
public static INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform)
|
||||
public static NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform)
|
||||
{
|
||||
return ToNotifyCollectionChanged(collection, transform, null!);
|
||||
}
|
||||
|
||||
public static INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
public static NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged<T, TView>(this IObservableCollection<T> collection, Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
// Optimized for non filtered
|
||||
return new NonFilteredNotifyCollectionChangedSynchronizedViewList<T, TView>(collection.CreateView(transform), collectionEventDispatcher);
|
||||
return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform), isSupportRangeFeature: false, collectionEventDispatcher, null);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
// Obsolete...
|
||||
[Obsolete("this interface is obsoleted. Use ISynchronizedViewFilter<T, TView> instead.")]
|
||||
public interface ISynchronizedViewFilter<T>
|
||||
{
|
||||
bool IsMatch(T value);
|
||||
}
|
||||
|
||||
public class SynchronizedViewFilter<T>(Func<T, bool> isMatch) : ISynchronizedViewFilter<T>
|
||||
public interface ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
public static readonly ISynchronizedViewFilter<T> Null = new NullViewFilter();
|
||||
bool IsMatch(T value, TView view);
|
||||
}
|
||||
|
||||
public bool IsMatch(T value) => isMatch(value);
|
||||
internal class SynchronizedViewValueOnlyFilter<T, TView>(Func<T, bool> isMatch) : ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
public bool IsMatch(T value, TView view) => isMatch(value);
|
||||
|
||||
class NullViewFilter : ISynchronizedViewFilter<T>
|
||||
class NullViewFilter : ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
public bool IsMatch(T value) => true;
|
||||
public bool IsMatch(T value, TView view) => true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SynchronizedViewFilter<T, TView>(Func<T, TView, bool> isMatch) : ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
public static readonly ISynchronizedViewFilter<T, TView> Null = new NullViewFilter();
|
||||
|
||||
public bool IsMatch(T value, TView view) => isMatch(value, view);
|
||||
|
||||
class NullViewFilter : ISynchronizedViewFilter<T, TView>
|
||||
{
|
||||
public bool IsMatch(T value, TView view) => true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,9 @@ namespace ObservableCollections
|
||||
Comparer = comparer ?? NullComparerSentinel.Instance;
|
||||
}
|
||||
|
||||
public (int Index, int Count, IComparer<T> Comparer) AsTuple()
|
||||
public (int Index, int Count, IComparer<T>? Comparer) AsTuple()
|
||||
{
|
||||
return (Index, Count, Comparer!);
|
||||
return (Index, Count, Comparer);
|
||||
}
|
||||
|
||||
public static SortOperation<T> CreateReverse(int index, int count)
|
||||
|
@ -7,7 +7,7 @@ using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableDictionary<TKey, TValue>
|
||||
public partial class ObservableDictionary<TKey, TValue>
|
||||
{
|
||||
public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform)
|
||||
{
|
||||
@ -19,7 +19,7 @@ namespace ObservableCollections
|
||||
{
|
||||
readonly ObservableDictionary<TKey, TValue> source;
|
||||
readonly Func<KeyValuePair<TKey, TValue>, TView> selector;
|
||||
ISynchronizedViewFilter<KeyValuePair<TKey, TValue>> filter;
|
||||
ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter;
|
||||
readonly Dictionary<TKey, (TValue, TView)> dict;
|
||||
int filteredCount;
|
||||
|
||||
@ -27,7 +27,7 @@ namespace ObservableCollections
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>>.Null;
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
@ -39,9 +39,10 @@ namespace ObservableCollections
|
||||
|
||||
public object SyncRoot { get; }
|
||||
public event NotifyViewChangedEventHandler<KeyValuePair<TKey, TValue>, TView>? ViewChanged;
|
||||
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public ISynchronizedViewFilter<KeyValuePair<TKey, TValue>> Filter
|
||||
public ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> Filter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
@ -73,7 +74,7 @@ namespace ObservableCollections
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<KeyValuePair<TKey, TValue>> filter)
|
||||
public void AttachFilter(ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter)
|
||||
{
|
||||
if (filter.IsNullFilter())
|
||||
{
|
||||
@ -88,7 +89,7 @@ namespace ObservableCollections
|
||||
foreach (var v in dict)
|
||||
{
|
||||
var value = new KeyValuePair<TKey, TValue>(v.Key, v.Value.Item1);
|
||||
if (filter.IsMatch(value))
|
||||
if (filter.IsMatch(value, v.Value.Item2))
|
||||
{
|
||||
filteredCount++;
|
||||
}
|
||||
@ -102,7 +103,7 @@ namespace ObservableCollections
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>>.Null;
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null;
|
||||
this.filteredCount = dict.Count;
|
||||
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<KeyValuePair<TKey, TValue>, TView>(NotifyCollectionChangedAction.Reset, true));
|
||||
}
|
||||
@ -110,17 +111,17 @@ namespace ObservableCollections
|
||||
|
||||
public ISynchronizedViewList<TView> ToViewList()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this);
|
||||
return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, isSupportRangeFeature: true);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, null);
|
||||
return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, isSupportRangeFeature: false);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, collectionEventDispatcher);
|
||||
return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher);
|
||||
}
|
||||
|
||||
public IEnumerator<TView> GetEnumerator()
|
||||
@ -130,7 +131,7 @@ namespace ObservableCollections
|
||||
foreach (var item in dict)
|
||||
{
|
||||
var v = (new KeyValuePair<TKey, TValue>(item.Key, item.Value.Item1), item.Value.Item2);
|
||||
if (filter.IsMatch(v.Item1))
|
||||
if (filter.IsMatch(v))
|
||||
{
|
||||
yield return v.Item2;
|
||||
}
|
||||
@ -149,7 +150,7 @@ namespace ObservableCollections
|
||||
foreach (var item in dict)
|
||||
{
|
||||
var v = (new KeyValuePair<TKey, TValue>(item.Key, item.Value.Item1), item.Value.Item2);
|
||||
if (filter.IsMatch(v.Item1))
|
||||
if (filter.IsMatch(v))
|
||||
{
|
||||
yield return v;
|
||||
}
|
||||
@ -184,14 +185,14 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = selector(e.NewItem);
|
||||
dict.Add(e.NewItem.Key, (e.NewItem.Value, v));
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, e.NewItem, v, -1);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, e.NewItem, v, -1);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (dict.Remove(e.OldItem.Key, out var v))
|
||||
{
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, e.OldItem, v.Item2, -1);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, e.OldItem, v.Item2, -1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -7,7 +7,7 @@ using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyObservableDictionary<TKey, TValue>
|
||||
public partial class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyObservableDictionary<TKey, TValue>
|
||||
where TKey : notnull
|
||||
{
|
||||
readonly Dictionary<TKey, TValue> dictionary;
|
||||
|
@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableFixedSizeRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
|
||||
public partial class ObservableFixedSizeRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly RingBuffer<T> buffer;
|
||||
readonly int capacity;
|
||||
|
@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableHashSet<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
public partial class ObservableHashSet<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
|
||||
{
|
||||
@ -17,7 +17,7 @@ namespace ObservableCollections
|
||||
|
||||
sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
public ISynchronizedViewFilter<T> Filter
|
||||
public ISynchronizedViewFilter<T, TView> Filter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
@ -27,9 +27,10 @@ namespace ObservableCollections
|
||||
readonly Dictionary<T, (T, TView)> dict;
|
||||
int filteredCount;
|
||||
|
||||
ISynchronizedViewFilter<T> filter;
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
|
||||
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
@ -38,7 +39,7 @@ namespace ObservableCollections
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
@ -70,7 +71,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T> filter)
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
if (filter.IsNullFilter())
|
||||
{
|
||||
@ -84,7 +85,7 @@ namespace ObservableCollections
|
||||
this.filteredCount = 0;
|
||||
foreach (var (_, (value, view)) in dict)
|
||||
{
|
||||
if (filter.IsMatch(value))
|
||||
if (filter.IsMatch(value, view))
|
||||
{
|
||||
filteredCount++;
|
||||
}
|
||||
@ -97,7 +98,7 @@ namespace ObservableCollections
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.filteredCount = dict.Count;
|
||||
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
|
||||
}
|
||||
@ -105,17 +106,17 @@ namespace ObservableCollections
|
||||
|
||||
public ISynchronizedViewList<TView> ToViewList()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher);
|
||||
}
|
||||
|
||||
public IEnumerator<TView> GetEnumerator()
|
||||
@ -124,7 +125,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
if (filter.IsMatch(item.Value.Item1))
|
||||
if (filter.IsMatch(item.Value))
|
||||
{
|
||||
yield return item.Value.Item2;
|
||||
}
|
||||
@ -142,7 +143,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
if (filter.IsMatch(item.Value.Item1))
|
||||
if (filter.IsMatch(item.Value))
|
||||
{
|
||||
yield return item.Value;
|
||||
}
|
||||
@ -181,7 +182,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
dict.Add(e.NewItem, v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, -1);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -190,7 +191,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
dict.Add(item, v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, i++);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -199,7 +200,7 @@ namespace ObservableCollections
|
||||
{
|
||||
if (dict.Remove(e.OldItem, out var value))
|
||||
{
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, value, -1);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, value, -1);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -208,7 +209,7 @@ namespace ObservableCollections
|
||||
{
|
||||
if (dict.Remove(item, out var value))
|
||||
{
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, value, -1);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, value, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ using System.Linq;
|
||||
namespace ObservableCollections
|
||||
{
|
||||
// can not implements ISet<T> because set operation can not get added/removed values.
|
||||
public sealed partial class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
public partial class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
where T : notnull
|
||||
{
|
||||
readonly HashSet<T> set;
|
||||
|
@ -7,7 +7,7 @@ using System.Text;
|
||||
|
||||
namespace ObservableCollections;
|
||||
|
||||
public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
|
||||
public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
|
||||
{
|
||||
// override extension methods(IObservableCollection.cs ObservableCollectionExtensions)
|
||||
|
||||
@ -21,12 +21,18 @@ public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableLis
|
||||
// return new NonFilteredSynchronizedViewList<T, TView>(collection.CreateView(transform));
|
||||
//}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged()
|
||||
/// <summary>
|
||||
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
|
||||
/// </summary>
|
||||
public NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim()
|
||||
{
|
||||
return new ObservableListSynchronizedViewList<T>(this, null);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
/// <summary>
|
||||
/// Create faster, compact INotifyCollectionChanged view, however it does not support ***Range.
|
||||
/// </summary>
|
||||
public NotifyCollectionChangedSynchronizedViewList<T> ToNotifyCollectionChangedSlim(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new ObservableListSynchronizedViewList<T>(this, collectionEventDispatcher);
|
||||
}
|
||||
@ -42,7 +48,7 @@ public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableLis
|
||||
//}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -50,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)
|
||||
{
|
||||
@ -155,130 +161,61 @@ 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);
|
||||
}
|
||||
public override void Insert(int index, T item)
|
||||
{
|
||||
parent.Insert(index, item);
|
||||
}
|
||||
|
||||
object? IList.this[int index]
|
||||
public override bool Remove(T item)
|
||||
{
|
||||
get
|
||||
{
|
||||
return this[index];
|
||||
}
|
||||
set => throw new NotSupportedException();
|
||||
return parent.Remove(item);
|
||||
}
|
||||
|
||||
static bool IsCompatibleObject(object? value)
|
||||
public override void RemoveAt(int index)
|
||||
{
|
||||
return value is T || value == null && default(T) == null;
|
||||
parent.RemoveAt(index);
|
||||
}
|
||||
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
public bool IsFixedSize => false;
|
||||
|
||||
public bool IsSynchronized => true;
|
||||
|
||||
public object SyncRoot => parent.SyncRoot;
|
||||
|
||||
public void Add(T item)
|
||||
public override void Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
parent.Clear();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -7,16 +7,48 @@ using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
|
||||
public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
|
||||
{
|
||||
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)
|
||||
{
|
||||
public ISynchronizedViewFilter<T> Filter
|
||||
return new View<TView>(this, transform);
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged()
|
||||
{
|
||||
return ToWritableNotifyCollectionChanged(null);
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<T> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return ToWritableNotifyCollectionChanged(
|
||||
static x => x,
|
||||
static (T newView, T originalValue, ref bool setValue) =>
|
||||
{
|
||||
setValue = true;
|
||||
return newView;
|
||||
},
|
||||
collectionEventDispatcher);
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter)
|
||||
{
|
||||
return ToWritableNotifyCollectionChanged(transform, converter, null!);
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged<TView>(Func<T, TView> transform, WritableViewChangedEventHandler<T, TView>? converter, ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new NonFilteredSynchronizedViewList<T, TView>(CreateView(transform), isSupportRangeFeature: false, collectionEventDispatcher, converter);
|
||||
}
|
||||
|
||||
internal sealed class View<TView> : ISynchronizedView<T, TView>, IWritableSynchronizedView<T, TView>
|
||||
{
|
||||
public ISynchronizedViewFilter<T, TView> Filter
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -29,9 +61,10 @@ namespace ObservableCollections
|
||||
internal readonly List<(T, TView)> list; // unsafe, be careful to use
|
||||
int filteredCount;
|
||||
|
||||
ISynchronizedViewFilter<T> filter;
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
|
||||
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
@ -40,7 +73,7 @@ namespace ObservableCollections
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
@ -72,7 +105,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T> filter)
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
if (filter.IsNullFilter())
|
||||
{
|
||||
@ -86,7 +119,7 @@ namespace ObservableCollections
|
||||
this.filteredCount = 0;
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (filter.IsMatch(list[i].Item1))
|
||||
if (filter.IsMatch(list[i]))
|
||||
{
|
||||
filteredCount++;
|
||||
}
|
||||
@ -100,7 +133,7 @@ namespace ObservableCollections
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.filteredCount = list.Count;
|
||||
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
|
||||
}
|
||||
@ -108,17 +141,17 @@ namespace ObservableCollections
|
||||
|
||||
public ISynchronizedViewList<TView> ToViewList()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher);
|
||||
}
|
||||
|
||||
public IEnumerator<TView> GetEnumerator()
|
||||
@ -127,7 +160,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (filter.IsMatch(item.Item1))
|
||||
if (filter.IsMatch(item))
|
||||
{
|
||||
yield return item.Item2;
|
||||
}
|
||||
@ -145,7 +178,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (filter.IsMatch(item.Item1))
|
||||
if (filter.IsMatch(item))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
@ -185,7 +218,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
list.Insert(e.NewStartingIndex, v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, e.NewStartingIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -202,7 +235,7 @@ namespace ObservableCollections
|
||||
var view = selector(item);
|
||||
views.Span[i] = view;
|
||||
valueViews.Span[i] = (item, view);
|
||||
var isMatch = matches.Span[i] = Filter.IsMatch(item);
|
||||
var isMatch = matches.Span[i] = Filter.IsMatch(item, view);
|
||||
if (isMatch)
|
||||
{
|
||||
filteredCount++; // increment in this process
|
||||
@ -214,7 +247,7 @@ namespace ObservableCollections
|
||||
}
|
||||
|
||||
list.InsertRange(e.NewStartingIndex, valueViews.Span);
|
||||
this.InvokeOnAddRange(ViewChanged, e.NewItems, views.Span, isMatchAll, matches.Span, e.NewStartingIndex);
|
||||
this.InvokeOnAddRange(ViewChanged, RejectedViewChanged, e.NewItems, views.Span, isMatchAll, matches.Span, e.NewStartingIndex);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
@ -222,7 +255,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = list[e.OldStartingIndex];
|
||||
list.RemoveAt(e.OldStartingIndex);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, e.OldStartingIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -238,7 +271,7 @@ namespace ObservableCollections
|
||||
var item = list[i];
|
||||
values.Span[j] = item.Item1;
|
||||
views.Span[j] = item.Item2;
|
||||
var isMatch = matches.Span[j] = Filter.IsMatch(item.Item1);
|
||||
var isMatch = matches.Span[j] = Filter.IsMatch(item);
|
||||
if (isMatch)
|
||||
{
|
||||
filteredCount--; // decrement in this process
|
||||
@ -251,7 +284,7 @@ namespace ObservableCollections
|
||||
}
|
||||
|
||||
list.RemoveRange(e.OldStartingIndex, e.OldItems.Length);
|
||||
this.InvokeOnRemoveRange(ViewChanged, values.Span, views.Span, isMatchAll, matches.Span, e.OldStartingIndex);
|
||||
this.InvokeOnRemoveRange(ViewChanged, RejectedViewChanged, values.Span, views.Span, isMatchAll, matches.Span, e.OldStartingIndex);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
@ -269,7 +302,7 @@ namespace ObservableCollections
|
||||
list.RemoveAt(e.OldStartingIndex);
|
||||
list.Insert(e.NewStartingIndex, removeItem);
|
||||
|
||||
this.InvokeOnMove(ref filteredCount, ViewChanged, removeItem, e.NewStartingIndex, e.OldStartingIndex);
|
||||
this.InvokeOnMove(ref filteredCount, ViewChanged, RejectedViewChanged, removeItem, e.NewStartingIndex, e.OldStartingIndex);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
@ -300,6 +333,112 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
#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 void AddToSourceCollection(T value)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
source.Add(value);
|
||||
}
|
||||
}
|
||||
public void InsertIntoSourceCollection(int index, T value)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
source.Insert(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveFromSourceCollection(T value)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return source.Remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAtSourceCollection(int index)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
source.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSourceCollection()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
source.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public IWritableSynchronizedViewList<TView> ToWritableViewList(WritableViewChangedEventHandler<T, TView> converter)
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true, converter: converter);
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this,
|
||||
isSupportRangeFeature: false,
|
||||
converter: static (TView newView, T originalValue, ref bool setValue) =>
|
||||
{
|
||||
setValue = true;
|
||||
return originalValue;
|
||||
});
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter)
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, converter: converter);
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this,
|
||||
isSupportRangeFeature: false,
|
||||
eventDispatcher: collectionEventDispatcher,
|
||||
converter: static (TView newView, T originalValue, ref bool setValue) =>
|
||||
{
|
||||
setValue = true;
|
||||
return originalValue;
|
||||
});
|
||||
}
|
||||
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler<T, TView> converter, ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher, converter);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
sealed class IgnoreViewComparer : IComparer<(T, TView)>
|
||||
{
|
||||
readonly IComparer<T> comparer;
|
||||
|
@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
|
||||
public partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
|
||||
{
|
||||
readonly List<T> list;
|
||||
public object SyncRoot { get; } = new();
|
||||
|
@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
public partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
|
||||
{
|
||||
@ -22,14 +22,15 @@ namespace ObservableCollections
|
||||
protected readonly Queue<(T, TView)> queue;
|
||||
int filteredCount;
|
||||
|
||||
ISynchronizedViewFilter<T> filter;
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
|
||||
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public ISynchronizedViewFilter<T> Filter
|
||||
public ISynchronizedViewFilter<T, TView> Filter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
@ -38,7 +39,7 @@ namespace ObservableCollections
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
@ -70,7 +71,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T> filter)
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
if (filter.IsNullFilter())
|
||||
{
|
||||
@ -84,7 +85,7 @@ namespace ObservableCollections
|
||||
this.filteredCount = 0;
|
||||
foreach (var (value, view) in queue)
|
||||
{
|
||||
if (filter.IsMatch(value))
|
||||
if (filter.IsMatch(value, view))
|
||||
{
|
||||
filteredCount++;
|
||||
}
|
||||
@ -97,7 +98,7 @@ namespace ObservableCollections
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.filteredCount = queue.Count;
|
||||
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
|
||||
}
|
||||
@ -105,17 +106,17 @@ namespace ObservableCollections
|
||||
|
||||
public ISynchronizedViewList<TView> ToViewList()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher);
|
||||
}
|
||||
|
||||
public IEnumerator<TView> GetEnumerator()
|
||||
@ -124,7 +125,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in queue)
|
||||
{
|
||||
if (filter.IsMatch(item.Item1))
|
||||
if (filter.IsMatch(item))
|
||||
{
|
||||
yield return item.Item2;
|
||||
}
|
||||
@ -142,7 +143,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in queue)
|
||||
{
|
||||
if (filter.IsMatch(item.Item1))
|
||||
if (filter.IsMatch(item))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
@ -182,7 +183,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
queue.Enqueue(v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, e.NewStartingIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -191,7 +192,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
queue.Enqueue(v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, i++);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -200,7 +201,7 @@ namespace ObservableCollections
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = queue.Dequeue();
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v.Item1, v.Item2, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -208,7 +209,7 @@ namespace ObservableCollections
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var v = queue.Dequeue();
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v.Item1, v.Item2, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -10,7 +10,7 @@ using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
public partial class ObservableQueue<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly Queue<T> queue;
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableRingBuffer<T>
|
||||
public partial class ObservableRingBuffer<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
|
||||
{
|
||||
@ -18,7 +18,7 @@ namespace ObservableCollections
|
||||
// used with ObservableFixedSizeRingBuffer
|
||||
internal sealed class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
public ISynchronizedViewFilter<T> Filter
|
||||
public ISynchronizedViewFilter<T, TView> Filter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
@ -28,9 +28,10 @@ namespace ObservableCollections
|
||||
readonly RingBuffer<(T, TView)> ringBuffer;
|
||||
int filteredCount;
|
||||
|
||||
ISynchronizedViewFilter<T> filter;
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
|
||||
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
@ -39,7 +40,7 @@ namespace ObservableCollections
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
@ -71,7 +72,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T> filter)
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
if (filter.IsNullFilter())
|
||||
{
|
||||
@ -86,7 +87,7 @@ namespace ObservableCollections
|
||||
for (var i = 0; i < ringBuffer.Count; i++)
|
||||
{
|
||||
var (value, view) = ringBuffer[i];
|
||||
if (filter.IsMatch(value))
|
||||
if (filter.IsMatch(value, view))
|
||||
{
|
||||
filteredCount++;
|
||||
}
|
||||
@ -99,7 +100,7 @@ namespace ObservableCollections
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.filteredCount = ringBuffer.Count;
|
||||
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
|
||||
}
|
||||
@ -107,22 +108,22 @@ namespace ObservableCollections
|
||||
|
||||
public ISynchronizedViewList<TView> ToViewList()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false);
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +133,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in ringBuffer)
|
||||
{
|
||||
if (filter.IsMatch(item.Item1))
|
||||
if (filter.IsMatch(item))
|
||||
{
|
||||
yield return item.Item2;
|
||||
}
|
||||
@ -150,7 +151,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in ringBuffer)
|
||||
{
|
||||
if (filter.IsMatch(item.Item1))
|
||||
if (filter.IsMatch(item))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
@ -196,7 +197,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
ringBuffer.AddFirst(v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -204,7 +205,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
ringBuffer.AddFirst(v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -215,7 +216,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
ringBuffer.AddLast(v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, ringBuffer.Count - 1);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, ringBuffer.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -223,7 +224,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
ringBuffer.AddLast(v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, ringBuffer.Count - 1);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, ringBuffer.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -236,14 +237,14 @@ namespace ObservableCollections
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = ringBuffer.RemoveFirst();
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v, 0);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < e.OldItems.Length; i++)
|
||||
{
|
||||
var v = ringBuffer.RemoveFirst();
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v, 0);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -254,7 +255,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var index = ringBuffer.Count - 1;
|
||||
var v = ringBuffer.RemoveLast();
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v, index);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -262,7 +263,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var index = ringBuffer.Count - 1;
|
||||
var v = ringBuffer.RemoveLast();
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v, index);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ using System.Collections;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
|
||||
public partial class ObservableRingBuffer<T> : IList<T>, IReadOnlyList<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly RingBuffer<T> buffer;
|
||||
|
||||
|
@ -7,7 +7,7 @@ using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
public partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
|
||||
{
|
||||
@ -21,14 +21,15 @@ namespace ObservableCollections
|
||||
protected readonly Stack<(T, TView)> stack;
|
||||
int filteredCount;
|
||||
|
||||
ISynchronizedViewFilter<T> filter;
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyViewChangedEventHandler<T, TView>? ViewChanged;
|
||||
public event Action<RejectedViewChangedAction, int, int>? RejectedViewChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public ISynchronizedViewFilter<T> Filter
|
||||
public ISynchronizedViewFilter<T, TView> Filter
|
||||
{
|
||||
get { lock (SyncRoot) return filter; }
|
||||
}
|
||||
@ -37,7 +38,7 @@ namespace ObservableCollections
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
@ -69,7 +70,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T> filter)
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
if (filter.IsNullFilter())
|
||||
{
|
||||
@ -83,7 +84,7 @@ namespace ObservableCollections
|
||||
this.filteredCount = 0;
|
||||
foreach (var (value, view) in stack)
|
||||
{
|
||||
if (filter.IsMatch(value))
|
||||
if (filter.IsMatch(value, view))
|
||||
{
|
||||
filteredCount++;
|
||||
}
|
||||
@ -96,7 +97,7 @@ namespace ObservableCollections
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<T>.Null;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
this.filteredCount = stack.Count;
|
||||
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
|
||||
}
|
||||
@ -104,22 +105,22 @@ namespace ObservableCollections
|
||||
|
||||
public ISynchronizedViewList<TView> ToViewList()
|
||||
{
|
||||
return new FiltableSynchronizedViewList<T, TView>(this);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: true);
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, null);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false);
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
public NotifyCollectionChangedSynchronizedViewList<TView> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedViewList<T, TView>(this, collectionEventDispatcher);
|
||||
return new FiltableSynchronizedViewList<T, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,7 +130,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in stack)
|
||||
{
|
||||
if (filter.IsMatch(item.Item1))
|
||||
if (filter.IsMatch(item))
|
||||
{
|
||||
yield return item.Item2;
|
||||
}
|
||||
@ -147,7 +148,7 @@ namespace ObservableCollections
|
||||
{
|
||||
foreach (var item in stack)
|
||||
{
|
||||
if (filter.IsMatch(item.Item1))
|
||||
if (filter.IsMatch(item))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
@ -187,7 +188,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
stack.Push(v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -195,7 +196,7 @@ namespace ObservableCollections
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
stack.Push(v);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0);
|
||||
this.InvokeOnAdd(ref filteredCount, ViewChanged, RejectedViewChanged, v, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -204,7 +205,7 @@ namespace ObservableCollections
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = stack.Pop();
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v.Item1, v.Item2, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -212,7 +213,7 @@ namespace ObservableCollections
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var v = stack.Pop();
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0);
|
||||
this.InvokeOnRemove(ref filteredCount, ViewChanged, RejectedViewChanged, v.Item1, v.Item2, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
public partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||
{
|
||||
readonly Stack<T> stack;
|
||||
public object SyncRoot { get; } = new object();
|
||||
@ -166,7 +166,7 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeek([MaybeNullWhen(false)] T result)
|
||||
public bool TryPeek([MaybeNullWhen(false)] out T result)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
|
@ -6,7 +6,7 @@ using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed class RingBuffer<T> : IList<T>, IReadOnlyList<T>
|
||||
public class RingBuffer<T> : IList<T>, IReadOnlyList<T>
|
||||
{
|
||||
T[] buffer;
|
||||
int head;
|
||||
|
@ -86,116 +86,126 @@ namespace ObservableCollections
|
||||
{
|
||||
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, bool> filter)
|
||||
{
|
||||
source.AttachFilter(new SynchronizedViewFilter<T>(filter));
|
||||
source.AttachFilter(new SynchronizedViewValueOnlyFilter<T, TView>(filter));
|
||||
}
|
||||
|
||||
public static bool IsNullFilter<T>(this ISynchronizedViewFilter<T> filter)
|
||||
public static void AttachFilter<T, TView>(this ISynchronizedView<T, TView> source, Func<T, TView, bool> filter)
|
||||
{
|
||||
return filter == SynchronizedViewFilter<T>.Null;
|
||||
source.AttachFilter(new SynchronizedViewFilter<T, TView>(filter));
|
||||
}
|
||||
|
||||
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, (T value, TView view) value, int index)
|
||||
public static bool IsNullFilter<T, TView>(this ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
InvokeOnAdd(collection, ref filteredCount, ev, value.value, value.view, index);
|
||||
return filter == SynchronizedViewFilter<T, TView>.Null;
|
||||
}
|
||||
|
||||
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, int index)
|
||||
internal static bool IsMatch<T, TView>(this ISynchronizedViewFilter<T, TView> filter, (T, TView) item)
|
||||
{
|
||||
var isMatch = collection.Filter.IsMatch(value);
|
||||
return filter.IsMatch(item.Item1, item.Item2);
|
||||
}
|
||||
|
||||
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, (T value, TView view) value, int index)
|
||||
{
|
||||
InvokeOnAdd(collection, ref filteredCount, ev, ev2, value.value, value.view, index);
|
||||
}
|
||||
|
||||
internal static void InvokeOnAdd<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int index)
|
||||
{
|
||||
var isMatch = collection.Filter.IsMatch(value, view);
|
||||
if (isMatch)
|
||||
{
|
||||
filteredCount++;
|
||||
if (ev != null)
|
||||
{
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
|
||||
}
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void InvokeOnAddRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
|
||||
{
|
||||
if (ev != null)
|
||||
else
|
||||
{
|
||||
if (isMatchAll)
|
||||
ev2?.Invoke(RejectedViewChangedAction.Add, index, -1);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void InvokeOnAddRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
|
||||
{
|
||||
if (isMatchAll)
|
||||
{
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, isSingleItem: false, newValues: values, newViews: views, newStartingIndex: index));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < matches.Length; i++)
|
||||
{
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, isSingleItem: false, newValues: values, newViews: views, newStartingIndex: index));
|
||||
}
|
||||
else
|
||||
{
|
||||
var startingIndex = index;
|
||||
for (var i = 0; i < matches.Length; i++)
|
||||
if (matches[i])
|
||||
{
|
||||
if (matches[i])
|
||||
{
|
||||
var item = (values[i], views[i]);
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: startingIndex++));
|
||||
}
|
||||
var item = (values[i], views[i]);
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: index));
|
||||
}
|
||||
else
|
||||
{
|
||||
ev2?.Invoke(RejectedViewChangedAction.Add, index, -1);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, (T value, TView view) value, int oldIndex)
|
||||
internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, (T value, TView view) value, int oldIndex)
|
||||
{
|
||||
InvokeOnRemove(collection, ref filteredCount, ev, value.value, value.view, oldIndex);
|
||||
InvokeOnRemove(collection, ref filteredCount, ev, ev2, value.value, value.view, oldIndex);
|
||||
}
|
||||
|
||||
internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, int oldIndex)
|
||||
internal static void InvokeOnRemove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int oldIndex)
|
||||
{
|
||||
var isMatch = collection.Filter.IsMatch(value);
|
||||
var isMatch = collection.Filter.IsMatch(value, view);
|
||||
if (isMatch)
|
||||
{
|
||||
filteredCount--;
|
||||
if (ev != null)
|
||||
{
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
|
||||
}
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
ev2?.Invoke(RejectedViewChangedAction.Remove, oldIndex, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// only use for ObservableList
|
||||
internal static void InvokeOnRemoveRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
|
||||
internal static void InvokeOnRemoveRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
|
||||
{
|
||||
if (ev != null)
|
||||
if (isMatchAll)
|
||||
{
|
||||
if (isMatchAll)
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, isSingleItem: false, oldValues: values, oldViews: views, oldStartingIndex: index));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < matches.Length; i++)
|
||||
{
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, isSingleItem: false, oldValues: values, oldViews: views, oldStartingIndex: index));
|
||||
}
|
||||
else
|
||||
{
|
||||
var startingIndex = index;
|
||||
for (var i = 0; i < matches.Length; i++)
|
||||
if (matches[i])
|
||||
{
|
||||
if (matches[i])
|
||||
{
|
||||
var item = (values[i], views[i]);
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, isSingleItem: true, oldItem: item, oldStartingIndex: index)); //remove for list, always same index
|
||||
}
|
||||
else
|
||||
{
|
||||
index++; // not matched, skip index
|
||||
}
|
||||
var item = (values[i], views[i]);
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, isSingleItem: true, oldItem: item, oldStartingIndex: index)); //remove for list, always same index
|
||||
}
|
||||
else
|
||||
{
|
||||
ev2?.Invoke(RejectedViewChangedAction.Remove, index, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void InvokeOnMove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, (T value, TView view) value, int index, int oldIndex)
|
||||
internal static void InvokeOnMove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, (T value, TView view) value, int index, int oldIndex)
|
||||
{
|
||||
InvokeOnMove(collection, ref filteredCount, ev, value.value, value.view, index, oldIndex);
|
||||
InvokeOnMove(collection, ref filteredCount, ev, ev2, value.value, value.view, index, oldIndex);
|
||||
}
|
||||
|
||||
internal static void InvokeOnMove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, int index, int oldIndex)
|
||||
internal static void InvokeOnMove<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, Action<RejectedViewChangedAction, int, int>? ev2, T value, TView view, int index, int oldIndex)
|
||||
{
|
||||
if (ev != null)
|
||||
// move does not changes filtered-count
|
||||
var isMatch = collection.Filter.IsMatch(value, view);
|
||||
if (isMatch)
|
||||
{
|
||||
// move does not changes filtered-count
|
||||
var isMatch = collection.Filter.IsMatch(value);
|
||||
if (isMatch)
|
||||
{
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex));
|
||||
}
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
ev2?.Invoke(RejectedViewChangedAction.Move, index, oldIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,35 +216,25 @@ namespace ObservableCollections
|
||||
|
||||
internal static void InvokeOnReplace<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1)
|
||||
{
|
||||
var oldMatched = collection.Filter.IsMatch(oldValue);
|
||||
var newMatched = collection.Filter.IsMatch(value);
|
||||
var oldMatched = collection.Filter.IsMatch(oldValue, oldView);
|
||||
var newMatched = collection.Filter.IsMatch(value, view);
|
||||
var bothMatched = oldMatched && newMatched;
|
||||
|
||||
if (bothMatched)
|
||||
{
|
||||
if (ev != null)
|
||||
{
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Replace, true, newItem: (value, view), oldItem: (oldValue, oldView), newStartingIndex: index, oldStartingIndex: oldIndex >= 0 ? oldIndex : index));
|
||||
}
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Replace, true, newItem: (value, view), oldItem: (oldValue, oldView), newStartingIndex: index, oldStartingIndex: oldIndex >= 0 ? oldIndex : index));
|
||||
}
|
||||
else if (oldMatched)
|
||||
{
|
||||
// only-old is remove
|
||||
filteredCount--;
|
||||
if (ev != null)
|
||||
{
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
|
||||
}
|
||||
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
|
||||
}
|
||||
else if (newMatched)
|
||||
{
|
||||
// only-new is add
|
||||
filteredCount++;
|
||||
if (ev != null)
|
||||
{
|
||||
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
|
||||
}
|
||||
ev?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user