From 135753b6a3bcdfbfe05bbac160ee30437bf57084 Mon Sep 17 00:00:00 2001 From: Pavel Leonenko Date: Thu, 27 Jun 2024 19:11:42 +0200 Subject: [PATCH] Added unsubscribing from CanExecute-related events when disposing SetCommandWithDisposing subscription --- .../DroidBindingFactory.cs | 39 +++++++++++++------ .../AppleBindingFactory.cs | 39 +++++++++++++------ Softeq.XToolkit.Bindings/BindingExtensions.cs | 16 +++++--- .../BindingFactoryBase.cs | 2 +- Softeq.XToolkit.Bindings/IBindingFactory.cs | 3 +- 5 files changed, 67 insertions(+), 32 deletions(-) diff --git a/Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs b/Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs index 2459e3fa0..96c834724 100644 --- a/Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs +++ b/Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs @@ -8,6 +8,7 @@ using System.Windows.Input; using Android.Views; using Android.Widget; +using Softeq.XToolkit.Common.Disposables; using Softeq.XToolkit.Common.Threading; namespace Softeq.XToolkit.Bindings.Droid @@ -147,18 +148,20 @@ public override Delegate GetCommandHandler( } /// - public override void HandleCommandCanExecute( + public override IDisposable HandleCommandCanExecute( object element, ICommand command, Binding? commandParameterBinding) { if (element is View view) { - HandleViewEnabled(view, command, commandParameterBinding); + return HandleViewEnabled(view, command, commandParameterBinding); } + + return Disposable.Create(() => { }); } - private static void HandleViewEnabled( + private static IDisposable HandleViewEnabled( View view, ICommand command, Binding? commandParameterBinding) @@ -171,20 +174,32 @@ private static void HandleViewEnabled( () => view.Enabled = command.CanExecute(commandParameter)); // set by CanExecute - command.CanExecuteChanged += (s, args) => - { - Execute.BeginOnUIThread( - () => view.Enabled = command.CanExecute(commandParameter)); - }; + command.CanExecuteChanged += OnCommandCanExecuteChanged; // set by bindable command parameter if (commandParameterBinding != null) { - commandParameterBinding.ValueChanged += (s, args) => + commandParameterBinding.ValueChanged += OnCommandParameterBindingValueChanged; + } + + return Disposable.Create(() => + { + if (commandParameterBinding != null) { - Execute.BeginOnUIThread( - () => view.Enabled = command.CanExecute(commandParameterBinding.Value)); - }; + commandParameterBinding.ValueChanged -= OnCommandParameterBindingValueChanged; + } + + command.CanExecuteChanged -= OnCommandCanExecuteChanged; + }); + + void OnCommandCanExecuteChanged(object? s, EventArgs args) + { + Execute.BeginOnUIThread(() => view.Enabled = command.CanExecute(commandParameter)); + } + + void OnCommandParameterBindingValueChanged(object? s, EventArgs args) + { + Execute.BeginOnUIThread(() => view.Enabled = command.CanExecute(commandParameterBinding.Value)); } } } diff --git a/Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs b/Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs index d0700aeea..38d8df550 100644 --- a/Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs +++ b/Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Windows.Input; +using Softeq.XToolkit.Common.Disposables; using UIKit; namespace Softeq.XToolkit.Bindings.iOS @@ -98,18 +99,20 @@ public override Binding CreateBinding( } /// - public override void HandleCommandCanExecute( + public override IDisposable HandleCommandCanExecute( object element, ICommand command, Binding? commandParameterBinding) { if (element is UIControl control) { - HandleControlEnabled(control, command, commandParameterBinding); + return HandleControlEnabled(control, command, commandParameterBinding); } + + return Disposable.Create(() => { }); } - private static void HandleControlEnabled( + private static IDisposable HandleControlEnabled( UIControl control, ICommand command, Binding? commandParameterBinding) @@ -122,20 +125,32 @@ private static void HandleControlEnabled( () => control.Enabled = command.CanExecute(commandParameter)); // set by CanExecute - command.CanExecuteChanged += (s, args) => - { - control.BeginInvokeOnMainThread( - () => control.Enabled = command.CanExecute(commandParameter)); - }; + command.CanExecuteChanged += OnCommandCanExecuteChanged; // set by bindable command parameter if (commandParameterBinding != null) { - commandParameterBinding.ValueChanged += (s, args) => + commandParameterBinding.ValueChanged += OnCommandParameterBindingValueChanged; + } + + return Disposable.Create(() => + { + if (commandParameterBinding != null) { - control.BeginInvokeOnMainThread( - () => control.Enabled = command.CanExecute(commandParameterBinding.Value)); - }; + commandParameterBinding.ValueChanged -= OnCommandParameterBindingValueChanged; + } + + command.CanExecuteChanged -= OnCommandCanExecuteChanged; + }); + + void OnCommandCanExecuteChanged(object? s, EventArgs args) + { + control.BeginInvokeOnMainThread(() => control.Enabled = command.CanExecute(commandParameter)); + } + + void OnCommandParameterBindingValueChanged(object? s, EventArgs args) + { + control.BeginInvokeOnMainThread(() => control.Enabled = command.CanExecute(commandParameterBinding.Value)); } } } diff --git a/Softeq.XToolkit.Bindings/BindingExtensions.cs b/Softeq.XToolkit.Bindings/BindingExtensions.cs index 1ed231d5d..c2828336c 100644 --- a/Softeq.XToolkit.Bindings/BindingExtensions.cs +++ b/Softeq.XToolkit.Bindings/BindingExtensions.cs @@ -565,9 +565,13 @@ public static IDisposable SetCommandWithDisposing( e.AddEventHandler(element, handler); - HandleCommandCanExecute(element, command); + var canExecuteSubscriptions = HandleCommandCanExecute(element, command); - return Disposable.Create(() => e.RemoveEventHandler(element, handler)); + return Disposable.Create(() => + { + canExecuteSubscriptions.Dispose(); + e.RemoveEventHandler(element, handler); + }); } /// @@ -659,19 +663,19 @@ internal static EventInfo GetEventInfoForControl(this Type type, string? eventNa return info; } - private static void HandleCommandCanExecute( + private static IDisposable HandleCommandCanExecute( object element, ICommand command) { - HandleCommandCanExecute(element, command); + return HandleCommandCanExecute(element, command); } - private static void HandleCommandCanExecute( + private static IDisposable HandleCommandCanExecute( object element, ICommand command, Binding? commandParameterBinding = null) { - BindingFactory.HandleCommandCanExecute(element, command, commandParameterBinding); + return BindingFactory.HandleCommandCanExecute(element, command, commandParameterBinding); } } } diff --git a/Softeq.XToolkit.Bindings/BindingFactoryBase.cs b/Softeq.XToolkit.Bindings/BindingFactoryBase.cs index d686f50eb..5ab10a8bd 100644 --- a/Softeq.XToolkit.Bindings/BindingFactoryBase.cs +++ b/Softeq.XToolkit.Bindings/BindingFactoryBase.cs @@ -49,7 +49,7 @@ public abstract Binding CreateBinding( public abstract string? GetDefaultEventNameForControl(Type type); /// - public abstract void HandleCommandCanExecute( + public abstract IDisposable HandleCommandCanExecute( object element, ICommand command, Binding? commandParameterBinding); diff --git a/Softeq.XToolkit.Bindings/IBindingFactory.cs b/Softeq.XToolkit.Bindings/IBindingFactory.cs index 1f23d10ac..b909b7736 100644 --- a/Softeq.XToolkit.Bindings/IBindingFactory.cs +++ b/Softeq.XToolkit.Bindings/IBindingFactory.cs @@ -248,6 +248,7 @@ Delegate GetCommandHandler( /// that will passed to the . Used to determine new value of CanExecute. /// /// Type of command parameter. - void HandleCommandCanExecute(object element, ICommand command, Binding? commandParameterBinding); + /// Disposable subscriptions to CanExecute related events. + IDisposable HandleCommandCanExecute(object element, ICommand command, Binding? commandParameterBinding); } }