diff --git a/Toolkit.Avalonia/AvaloniaDispatcher.cs b/Toolkit.Avalonia/AvaloniaDispatcher.cs index 7d63401..4e2a213 100644 --- a/Toolkit.Avalonia/AvaloniaDispatcher.cs +++ b/Toolkit.Avalonia/AvaloniaDispatcher.cs @@ -6,6 +6,9 @@ namespace Toolkit.Avalonia; public class AvaloniaDispatcher : IDispatcher { + public bool CheckAccess() => + Dispatcher.UIThread.CheckAccess(); + public async Task Invoke(Action action) => await Dispatcher.UIThread.InvokeAsync(action); } \ No newline at end of file diff --git a/Toolkit.Foundation/IDispatcher.cs b/Toolkit.Foundation/IDispatcher.cs index 86790e5..afa756b 100644 --- a/Toolkit.Foundation/IDispatcher.cs +++ b/Toolkit.Foundation/IDispatcher.cs @@ -3,4 +3,6 @@ public interface IDispatcher { Task Invoke(Action action); + + bool CheckAccess(); } \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceScopeFactory.cs b/Toolkit.Foundation/IServiceScopeFactory.cs index b6fb263..daf7284 100644 --- a/Toolkit.Foundation/IServiceScopeFactory.cs +++ b/Toolkit.Foundation/IServiceScopeFactory.cs @@ -2,7 +2,7 @@ namespace Toolkit.Foundation; -public interface IScopeServiceFactory +public interface IServiceScopeFactory { (IServiceScope, TService) Create(params object?[] parameters); } \ No newline at end of file diff --git a/Toolkit.Foundation/ObservableCollection.cs b/Toolkit.Foundation/ObservableCollection.cs index 0d46faa..5c4f248 100644 --- a/Toolkit.Foundation/ObservableCollection.cs +++ b/Toolkit.Foundation/ObservableCollection.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.Messaging; using Microsoft.Extensions.DependencyInjection; using System.Collections; using System.Collections.Specialized; +using System.Reactive.Concurrency; using System.Reactive.Disposables; namespace Toolkit.Foundation; @@ -32,6 +33,8 @@ public abstract partial class ObservableCollection : { private readonly System.Collections.ObjectModel.ObservableCollection collection = []; + private readonly Lock syncLock = new(); + private readonly IDispatcher dispatcher; private readonly Dictionary trackedProperties = []; @@ -95,8 +98,27 @@ public abstract partial class ObservableCollection : public TViewModel this[int index] { - get => collection[index]; - set => SetItem(index, value); + get + { + lock (syncLock) + { + return collection[index]; + } + } + set + { + if (dispatcher.CheckAccess()) + { + lock (syncLock) + { + SetItem(index, value); + } + } + else + { + dispatcher.Invoke(() => SetItem(index, value)); + } + } } object? IList.this[int index] @@ -129,10 +151,18 @@ public abstract partial class ObservableCollection : public void Add(TViewModel item) { - int index = collection.Count; - InsertItem(index, item); - - UpdateSelection(item); + if (dispatcher.CheckAccess()) + { + lock (syncLock) + { + InsertItem(collection.Count, item); + UpdateSelection(item); + } + } + else + { + dispatcher.Invoke(() => Add(item)); + } } public void Add(object item) @@ -263,36 +293,32 @@ public abstract partial class ObservableCollection : public bool Move(int oldIndex, int newIndex) { - if (oldIndex < 0) + if (dispatcher.CheckAccess()) { - return false; - } - - TViewModel item = this[oldIndex]; - - bool moveSelection = false; - if (item is ISelectable oldSelection) - { - if (oldSelection.IsSelected) + lock (syncLock) { - moveSelection = true; - SelectedItem = default; + if (oldIndex < 0 || newIndex < 0 || oldIndex >= Count || newIndex >= Count) + { + return false; + } + + TViewModel item = collection[oldIndex]; + collection.Move(oldIndex, newIndex); + + if (item is ISelectable selectable && selectable.IsSelected) + { + dispatcher.Invoke(() => SelectedItem = item); + } + + return true; } } - - RemoveItem(oldIndex); - InsertItem(newIndex, item); - - if (moveSelection) + else { - if (item is ISelectable newSelection) - { - newSelection.IsSelected = true; - dispatcher.Invoke(() => SelectedItem = item); - } + bool result = false; + dispatcher.Invoke(() => result = Move(oldIndex, newIndex)); + return result; } - - return true; } public bool Move(int index, TViewModel item) @@ -368,26 +394,38 @@ public abstract partial class ObservableCollection : public bool Remove(TViewModel item) { - int index = collection.IndexOf(item); - if (index < 0) + if (dispatcher.CheckAccess()) { - return false; + lock (syncLock) + { + int index = collection.IndexOf(item); + if (index < 0) + { + return false; + } + + Disposer.Dispose(item); + Disposer.Remove(this, item); + + TViewModel? oldSelection = SelectedItem; + RemoveItem(index); + + if (item.Equals(oldSelection)) + { + int newIndex = Math.Min(index, Count - 1); + TViewModel? selectedItem = newIndex >= 0 ? this[newIndex] : default; + SelectedItem = selectedItem; + } + + return true; + } } - - Disposer.Dispose(item); - Disposer.Remove(this, item); - - TViewModel? oldSelection = SelectedItem; - RemoveItem(index); - - if (item.Equals(oldSelection)) + else { - int newIndex = Math.Min(index, Count - 1); - TViewModel? selectedItem = newIndex >= 0 ? this[newIndex] : default; - dispatcher.Invoke(() => SelectedItem = selectedItem); + bool result = false; + dispatcher.Invoke(() => result = Remove(item)); + return result; } - - return true; } void IList.Remove(object? value) @@ -421,22 +459,34 @@ public abstract partial class ObservableCollection : return true; } - public bool Replace(int index, - TViewModel item) + public bool Replace(int index, TViewModel item) { - if (index <= Count - 1) + if (dispatcher.CheckAccess()) { - RemoveItem(index); + lock (syncLock) + { + if (index <= Count - 1) + { + RemoveItem(index); + } + else + { + index = Count; + } + + Insert(index, item); + return true; + } } else { - index = Count; + bool result = false; + dispatcher.Invoke(() => result = Replace(index, item)); + return result; } - - Insert(index, item); - return true; } + public void Revert() { foreach (object trackedProperty in trackedProperties.Values) diff --git a/Toolkit.Foundation/ScopeServiceFactory.cs b/Toolkit.Foundation/ServiceScopeFactory.cs similarity index 88% rename from Toolkit.Foundation/ScopeServiceFactory.cs rename to Toolkit.Foundation/ServiceScopeFactory.cs index f4b4bd1..10efe92 100644 --- a/Toolkit.Foundation/ScopeServiceFactory.cs +++ b/Toolkit.Foundation/ServiceScopeFactory.cs @@ -2,9 +2,9 @@ namespace Toolkit.Foundation; -public class ScopeServiceFactory(IServiceScopeFactory serviceScopeFactory, +public class ServiceScopeFactory(IServiceScopeFactory serviceScopeFactory, ICache cache) : - IScopeServiceFactory + IServiceScopeFactory where TService : notnull { public (IServiceScope, TService) Create(params object?[] parameters) diff --git a/Toolkit.WinUI/WinUIDispatcher.cs b/Toolkit.WinUI/WinUIDispatcher.cs index 26867e8..0f308de 100644 --- a/Toolkit.WinUI/WinUIDispatcher.cs +++ b/Toolkit.WinUI/WinUIDispatcher.cs @@ -6,6 +6,8 @@ namespace Toolkit.WinUI; public class WinUIDispatcher(DispatcherQueue dispatcherQueue) : IDispatcher { + public bool CheckAccess() => dispatcherQueue.HasThreadAccess; + public Task Invoke(Action action) { dispatcherQueue.TryEnqueue(action.Invoke);