From c24538f5459e4eb8cbb33b078f0a1e0664fd2d0a Mon Sep 17 00:00:00 2001 From: TheXamlGuy Date: Fri, 31 May 2024 22:50:52 +0100 Subject: [PATCH] Fix more edge cases --- Toolkit.Foundation/AggerateAttribute.cs | 6 +- Toolkit.Foundation/AggerateEventArgs.cs | 31 ------- Toolkit.Foundation/AggerateMode.cs | 7 -- Toolkit.Foundation/Aggregate.cs | 10 +++ Toolkit.Foundation/AggregateEventArgs.cs | 8 ++ Toolkit.Foundation/AggregateExpression.cs | 3 + Toolkit.Foundation/HandlerProvider.cs | 1 + Toolkit.Foundation/IAggerate.cs | 6 -- Toolkit.Foundation/IAggregate.cs | 3 + Toolkit.Foundation/IPublisher.cs | 8 +- Toolkit.Foundation/ObservableCollection.cs | 27 +++--- Toolkit.Foundation/Publisher.cs | 9 +- Toolkit.Foundation/Subscription.cs | 89 +++++++++++++------ Toolkit.UI.Avalonia/AttachedBehaviour.cs | 3 +- Toolkit.UI.Avalonia/ListBoxExtension.cs | 77 ++++++++++++++++ .../NavigationViewItemExtension.cs | 42 ++++----- 16 files changed, 212 insertions(+), 118 deletions(-) delete mode 100644 Toolkit.Foundation/AggerateEventArgs.cs delete mode 100644 Toolkit.Foundation/AggerateMode.cs create mode 100644 Toolkit.Foundation/Aggregate.cs create mode 100644 Toolkit.Foundation/AggregateEventArgs.cs create mode 100644 Toolkit.Foundation/AggregateExpression.cs delete mode 100644 Toolkit.Foundation/IAggerate.cs create mode 100644 Toolkit.Foundation/IAggregate.cs create mode 100644 Toolkit.UI.Avalonia/ListBoxExtension.cs diff --git a/Toolkit.Foundation/AggerateAttribute.cs b/Toolkit.Foundation/AggerateAttribute.cs index f59bb94..36bc641 100644 --- a/Toolkit.Foundation/AggerateAttribute.cs +++ b/Toolkit.Foundation/AggerateAttribute.cs @@ -1,7 +1,3 @@ namespace Toolkit.Foundation; -public class AggerateAttribute(Type type, object key, - AggerateMode mode = AggerateMode.Reset) : NotificationAttribute(type, key) -{ - public AggerateMode Mode => mode; -} \ No newline at end of file +public class AggerateAttribute(Type type, object key) : NotificationAttribute(type, key); \ No newline at end of file diff --git a/Toolkit.Foundation/AggerateEventArgs.cs b/Toolkit.Foundation/AggerateEventArgs.cs deleted file mode 100644 index 650e0b1..0000000 --- a/Toolkit.Foundation/AggerateEventArgs.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Toolkit.Foundation; - -public record Aggerate -{ - public static AggerateEventArgs With(TOptions options) where TOptions : class - { - return new AggerateEventArgs(options); - } - - public static AggerateEventArgs With() - { - return new AggerateEventArgs(); - } -} - -public record AggerateEventArgs(TOptions? Options = null) : - IAggerate - where TOptions : class -{ - public object? Key { get; init; } - - public AggerateMode Mode { get; init; } -} - -public record AggerateEventArgs : - IAggerate -{ - public object? Key { get; init; } - - public AggerateMode Mode { get; init; } -} \ No newline at end of file diff --git a/Toolkit.Foundation/AggerateMode.cs b/Toolkit.Foundation/AggerateMode.cs deleted file mode 100644 index 7b91899..0000000 --- a/Toolkit.Foundation/AggerateMode.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Toolkit.Foundation; - -public enum AggerateMode -{ - Append, - Reset -} \ No newline at end of file diff --git a/Toolkit.Foundation/Aggregate.cs b/Toolkit.Foundation/Aggregate.cs new file mode 100644 index 0000000..ebbd1df --- /dev/null +++ b/Toolkit.Foundation/Aggregate.cs @@ -0,0 +1,10 @@ +namespace Toolkit.Foundation; + +public record Aggregate +{ + public static AggregateEventArgs As(TOptions options) + where TOptions : class => new(options); + + public static AggerateEventArgs As() => + new AggerateEventArgs(); +} diff --git a/Toolkit.Foundation/AggregateEventArgs.cs b/Toolkit.Foundation/AggregateEventArgs.cs new file mode 100644 index 0000000..9c4a6b2 --- /dev/null +++ b/Toolkit.Foundation/AggregateEventArgs.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record AggregateEventArgs(TOptions? Options = null) : + IAggregate + where TOptions : class; + +public record AggerateEventArgs : + IAggregate; \ No newline at end of file diff --git a/Toolkit.Foundation/AggregateExpression.cs b/Toolkit.Foundation/AggregateExpression.cs new file mode 100644 index 0000000..3c70c3e --- /dev/null +++ b/Toolkit.Foundation/AggregateExpression.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public record AggregateExpression(IAggregate Value, object? Key = null); diff --git a/Toolkit.Foundation/HandlerProvider.cs b/Toolkit.Foundation/HandlerProvider.cs index 68cb161..a8f7939 100644 --- a/Toolkit.Foundation/HandlerProvider.cs +++ b/Toolkit.Foundation/HandlerProvider.cs @@ -6,6 +6,7 @@ public class HandlerProvider(SubscriptionCollection subscriptions) : public IEnumerable Get(Type type, object? key = null) { + var d = subscriptions; string subscriptionKey = $"{(key is not null ? $"{key}:" : "")}{type}"; if (subscriptions.TryGetValue(subscriptionKey, out List? subscribers)) { diff --git a/Toolkit.Foundation/IAggerate.cs b/Toolkit.Foundation/IAggerate.cs deleted file mode 100644 index a3d9453..0000000 --- a/Toolkit.Foundation/IAggerate.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Toolkit.Foundation; - -public interface IAggerate -{ - object? Key { get; init; } -} \ No newline at end of file diff --git a/Toolkit.Foundation/IAggregate.cs b/Toolkit.Foundation/IAggregate.cs new file mode 100644 index 0000000..8ca8471 --- /dev/null +++ b/Toolkit.Foundation/IAggregate.cs @@ -0,0 +1,3 @@ +namespace Toolkit.Foundation; + +public interface IAggregate; \ No newline at end of file diff --git a/Toolkit.Foundation/IPublisher.cs b/Toolkit.Foundation/IPublisher.cs index 2980f7d..88d1411 100644 --- a/Toolkit.Foundation/IPublisher.cs +++ b/Toolkit.Foundation/IPublisher.cs @@ -2,14 +2,14 @@ public interface IPublisher { - void Publish(object key) + void Publish(object? key = null) where TMessage : new(); void Publish(TMessage message) where TMessage : notnull; void Publish(TMessage message, - object key) + object? key = null) where TMessage : notnull; void Publish(object message, @@ -22,9 +22,9 @@ public interface IPublisher void Publish(object message); void PublishUI(TMessage message, - object key) where TMessage : notnull; + object? key = null) where TMessage : notnull; - void PublishUI(object key) + void PublishUI(object? key = null) where TMessage : new(); void PublishUI(TMessage message) diff --git a/Toolkit.Foundation/ObservableCollection.cs b/Toolkit.Foundation/ObservableCollection.cs index 987165c..67891bd 100644 --- a/Toolkit.Foundation/ObservableCollection.cs +++ b/Toolkit.Foundation/ObservableCollection.cs @@ -192,20 +192,20 @@ public partial class ObservableCollection : } } - public void BeginAggregation() + public void Fetch(bool reset = false) { - if (this.GetAttribute() is AggerateAttribute attribute) + if (reset) { - if (attribute.Mode == AggerateMode.Reset) - { - Clear(); - } - - object? key = this.GetPropertyValue(() => attribute.Key) is { } value ? value : attribute.Key; - Publisher.PublishUI(OnAggerate(key)); + Clear(); } + + AggregateExpression expression = CreateAggregateExpression(); + Publisher.PublishUI(expression.Value, expression.Key); } + protected virtual IAggregate OnAggerate() => + new AggerateEventArgs(); + public void Clear() { clearing = true; @@ -389,7 +389,7 @@ public partial class ObservableCollection : } Initialized = true; - BeginAggregation(); + Fetch(); return Task.CompletedTask; } @@ -565,8 +565,11 @@ public partial class ObservableCollection : collection.Insert(index > Count ? Count : index, item); } - protected virtual IAggerate OnAggerate(object? key) => - new AggerateEventArgs() with { Key = key }; + protected virtual AggregateExpression CreateAggregateExpression() => + new AggregateExpression(new AggerateEventArgs()); + + protected virtual object? CreateAggregationKey() => + default; protected virtual void RemoveItem(int index) => collection.RemoveAt(index); diff --git a/Toolkit.Foundation/Publisher.cs b/Toolkit.Foundation/Publisher.cs index 9047ee7..41efe72 100644 --- a/Toolkit.Foundation/Publisher.cs +++ b/Toolkit.Foundation/Publisher.cs @@ -9,7 +9,7 @@ public class Publisher(IHandlerProvider handlerProvider, IDispatcher dispatcher) : IPublisher { - public void Publish(object key) + public void Publish(object? key = null) where TMessage : new() => Publish(serviceFactory.Create() ?? new TMessage(), async args => await args(), key); @@ -17,7 +17,8 @@ public class Publisher(IHandlerProvider handlerProvider, where TMessage : notnull => Publish(message, async args => await args(), null); - public void Publish(TMessage message, object key) + public void Publish(TMessage message, + object? key = null) where TMessage : notnull => Publish(message, async args => await args(), key); @@ -60,7 +61,7 @@ public class Publisher(IHandlerProvider handlerProvider, where TMessage : new() => Publish(new TMessage(), async args => await args(), null); - public void PublishUI(object key) + public void PublishUI(object? key = null) where TMessage : new() => Publish(new TMessage(), args => dispatcher.Invoke(async () => await args()), key); @@ -69,7 +70,7 @@ public class Publisher(IHandlerProvider handlerProvider, Publish(message, args => dispatcher.Invoke(async () => await args()), null); public void PublishUI(TMessage message, - object key) + object? key = null) where TMessage : notnull => Publish(message, args => dispatcher.Invoke(async () => await args()), key); diff --git a/Toolkit.Foundation/Subscription.cs b/Toolkit.Foundation/Subscription.cs index 9913398..10878f3 100644 --- a/Toolkit.Foundation/Subscription.cs +++ b/Toolkit.Foundation/Subscription.cs @@ -10,21 +10,37 @@ public class Subscription(SubscriptionCollection subscriptions, { Type handlerType = subscriber.GetType(); - IDictionary keys = GetKeysFromHandler(subscriber); + IDictionary> subscribers = GetSubscriptionKeys(subscriber); foreach (Type interfaceType in GetHandlerInterfaces(handlerType)) { if (interfaceType.GetGenericArguments().FirstOrDefault() is Type argumentType) { - keys.TryGetValue(argumentType, out object? key); - - string subscriptionKey = $"{(key is not null ? $"{key}:" : "")}{argumentType}"; - subscriptions.AddOrUpdate(subscriptionKey, _ => new List { new(subscriber) }, (_, collection) => + subscribers.TryGetValue(argumentType, out List? keys); + if (keys is not null) { - collection.Add(new WeakReference(subscriber)); - return collection; - }); + foreach (object key in keys) + { + string subscriptionKey = $"{(key is not null ? $"{key}:" : "")}{argumentType}"; + subscriptions.AddOrUpdate(subscriptionKey, _ => new List { new(subscriber) }, (_, collection) => + { + collection.Add(new WeakReference(subscriber)); + return collection; + }); - disposer.Add(subscriber, Disposable.Create(() => RemoveSubscriber(subscriber, subscriptionKey))); + disposer.Add(subscriber, Disposable.Create(() => RemoveSubscriber(subscriber, subscriptionKey))); + } + } + else + { + string subscriptionKey = $"{argumentType}"; + subscriptions.AddOrUpdate(subscriptionKey, _ => new List { new(subscriber) }, (_, collection) => + { + collection.Add(new WeakReference(subscriber)); + return collection; + }); + + disposer.Add(subscriber, Disposable.Create(() => RemoveSubscriber(subscriber, subscriptionKey))); + } } } } @@ -32,21 +48,40 @@ public class Subscription(SubscriptionCollection subscriptions, public void Remove(object subscriber) { Type handlerType = subscriber.GetType(); - IDictionary keys = GetKeysFromHandler(subscriber); + IDictionary> subscribers = GetSubscriptionKeys(subscriber); foreach (Type interfaceType in GetHandlerInterfaces(handlerType)) { if (interfaceType.GetGenericArguments().FirstOrDefault() is Type argumentType) { - keys.TryGetValue(argumentType, out object? key); - - string subscriptionKey = $"{(key is not null ? $"{key}:" : "")}{argumentType}"; - if (subscriptions.TryGetValue(subscriptionKey, out List? subscribers)) + subscribers.TryGetValue(argumentType, out List? keys); + if (keys is not null) { - for (int i = subscribers.Count - 1; i >= 0; i--) + foreach (object key in keys) { - if (!subscribers[i].IsAlive || subscribers[i].Target == subscriber) + string subscriptionKey = $"{(key is not null ? $"{key}:" : "")}{argumentType}"; + if (subscriptions.TryGetValue(subscriptionKey, out List? existing)) { - subscribers.RemoveAt(i); + for (int i = existing.Count - 1; i >= 0; i--) + { + if (!existing[i].IsAlive || existing[i].Target == subscriber) + { + existing.RemoveAt(i); + } + } + } + } + } + else + { + string subscriptionKey = $"{argumentType}"; + if (subscriptions.TryGetValue(subscriptionKey, out List? existing)) + { + for (int i = existing.Count - 1; i >= 0; i--) + { + if (!existing[i].IsAlive || existing[i].Target == subscriber) + { + existing.RemoveAt(i); + } } } } @@ -74,23 +109,23 @@ public class Subscription(SubscriptionCollection subscriptions, } } - //private object? GetKeyFromHandler(object handler) => - // handler.GetAttribute() is NotificationAttribute attribute - // ? handler.GetPropertyValue(() => attribute.Key) is { } value ? value : attribute.Key : null; - - private IDictionary GetKeysFromHandler(object handler) + private IDictionary> GetSubscriptionKeys(object subscriber) { - Dictionary keys = []; - - foreach (NotificationAttribute attribute in handler.GetAttributes()) + Dictionary> keys = []; + foreach (NotificationAttribute attribute in subscriber.GetAttributes()) { - keys.Add(attribute.Type, attribute.Key); + if (!keys.TryGetValue(attribute.Type, out List? value)) + { + value = ([]); + keys[attribute.Type] = value; + } + + value.Add(attribute.Key); } return keys; } - private IEnumerable GetHandlerInterfaces(Type handlerType) => handlerType.GetInterfaces().Where(interfaceType => { diff --git a/Toolkit.UI.Avalonia/AttachedBehaviour.cs b/Toolkit.UI.Avalonia/AttachedBehaviour.cs index 8454219..05aac5e 100644 --- a/Toolkit.UI.Avalonia/AttachedBehaviour.cs +++ b/Toolkit.UI.Avalonia/AttachedBehaviour.cs @@ -2,7 +2,8 @@ namespace Toolkit.UI.Avalonia; -public class AttachedBehaviour : Trigger +public class AttachedBehaviour : + Trigger { protected override void OnAttachedToVisualTree() { diff --git a/Toolkit.UI.Avalonia/ListBoxExtension.cs b/Toolkit.UI.Avalonia/ListBoxExtension.cs new file mode 100644 index 0000000..62e5fc0 --- /dev/null +++ b/Toolkit.UI.Avalonia/ListBoxExtension.cs @@ -0,0 +1,77 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Toolkit.UI.Controls.Avalonia; + +namespace Toolkit.UI.Avalonia; + +public class ListBoxExtension +{ + public static readonly AttachedProperty IsItemInvokedEnabledProperty = + AvaloniaProperty.RegisterAttached("IsItemInvokedEnabled", + typeof(ListBoxExtension), false); + + public static readonly RoutedEvent ItemInvokedEvent = + RoutedEvent.Register("ItemInvoked", + RoutingStrategies.Bubble, typeof(ListBoxExtension)); + + static ListBoxExtension() + { + IsItemInvokedEnabledProperty.Changed.AddClassHandler(OnIsItemClickEnabledPropertyChanged); + } + + private static void OnIsItemClickEnabledPropertyChanged(ListBoxItem sender, + AvaloniaPropertyChangedEventArgs args) + { + bool TrySetupListBox() + { + if (sender.GetLogicalAncestors().OfType().FirstOrDefault() is ListBox listBox) + { + void OnItemInvoked(object? _, SelectionChangedEventArgs args) + { + if (args.AddedItems is { Count: > 0 }) + { + if (sender.DataContext == listBox.SelectedItem) + { + sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemInvokedEvent }); + } + } + } + + if (sender.DataContext == listBox.SelectedItem) + { + sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemInvokedEvent }); + } + + listBox.SelectionChanged += OnItemInvoked; + return true; + } + + return false; + } + + if (!TrySetupListBox()) + { + void OnAttachedToVisualTree(object? _, VisualTreeAttachmentEventArgs __) + { + sender.AttachedToVisualTree -= OnAttachedToVisualTree; + TrySetupListBox(); + } + + sender.AttachedToVisualTree += OnAttachedToVisualTree; + } + } + + public static bool GetIsItemInvokedEnabled(ListBoxItem element) => + element.GetValue(IsItemInvokedEnabledProperty); + + public static void SetIsItemInvokedEnabled(ListBoxItem element, bool value) => + element.SetValue(IsItemInvokedEnabledProperty, value); + + public static void AddItemInvokedHandler(ListBoxItem element, EventHandler handler) => + element.AddHandler(ItemInvokedEvent, handler); + + public static void RemoveItemInvokedHandler(ListBoxItem element, EventHandler handler) => + element.RemoveHandler(ItemInvokedEvent, handler); +} \ No newline at end of file diff --git a/Toolkit.UI.Avalonia/NavigationViewItemExtension.cs b/Toolkit.UI.Avalonia/NavigationViewItemExtension.cs index e2ee3bc..8e8c452 100644 --- a/Toolkit.UI.Avalonia/NavigationViewItemExtension.cs +++ b/Toolkit.UI.Avalonia/NavigationViewItemExtension.cs @@ -5,22 +5,22 @@ using Toolkit.UI.Controls.Avalonia; namespace Toolkit.UI.Avalonia; -public class NavigationViewItemExtension +public class NavigationViewExtension { - public static readonly AttachedProperty IsItemClickEnabledProperty = - AvaloniaProperty.RegisterAttached("IsItemClickEnabled", - typeof(NavigationViewItemExtension), false); + public static readonly AttachedProperty IsItemInvokedEnabledProperty = + AvaloniaProperty.RegisterAttached("IsItemInvokedEnabled", + typeof(NavigationViewExtension), false); - public static readonly RoutedEvent ItemClickEvent = - RoutedEvent.Register("ItemClick", - RoutingStrategies.Bubble, typeof(NavigationViewItemExtension)); + public static readonly RoutedEvent ItemInvokedEvent = + RoutedEvent.Register("ItemInvoked", + RoutingStrategies.Bubble, typeof(NavigationViewExtension)); - static NavigationViewItemExtension() + static NavigationViewExtension() { - IsItemClickEnabledProperty.Changed.AddClassHandler(OnIsItemClickEnabledPropertyChanged); + IsItemInvokedEnabledProperty.Changed.AddClassHandler(OnIsItemInvokedEnabledPropertyChanged); } - private static void OnIsItemClickEnabledPropertyChanged(NavigationViewItem sender, + private static void OnIsItemInvokedEnabledPropertyChanged(NavigationViewItem sender, AvaloniaPropertyChangedEventArgs args) { bool TrySetupNavigationView() @@ -31,10 +31,10 @@ public class NavigationViewItemExtension { if (args.InvokedItemContainer == sender) { - sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemClickEvent }); + sender.RaiseEvent(new ItemInvokedEventArgs { RoutedEvent = ItemInvokedEvent }); } } - + navigationView.ItemInvoked += OnItemInvoked; return true; } @@ -54,15 +54,15 @@ public class NavigationViewItemExtension } } - public static bool GetIsItemClickEnabled(NavigationViewItem element) => - element.GetValue(IsItemClickEnabledProperty); + public static bool GetIsItemInvokedEnabled(NavigationViewItem element) => + element.GetValue(IsItemInvokedEnabledProperty); - public static void SetIsItemClickEnabled(NavigationViewItem element, bool value) => - element.SetValue(IsItemClickEnabledProperty, value); + public static void SetIsItemInvokedEnabled(NavigationViewItem element, bool value) => + element.SetValue(IsItemInvokedEnabledProperty, value); - public static void AddItemClickHandler(NavigationViewItem element, EventHandler handler) => - element.AddHandler(ItemClickEvent, handler); + public static void AddItemInvokedHandler(NavigationViewItem element, EventHandler handler) => + element.AddHandler(ItemInvokedEvent, handler); - public static void RemoveItemClickHandler(NavigationViewItem element, EventHandler handler) => - element.RemoveHandler(ItemClickEvent, handler); -} \ No newline at end of file + public static void RemoveItemInvokedHandler(NavigationViewItem element, EventHandler handler) => + element.RemoveHandler(ItemInvokedEvent, handler); +}