Skip to content

Commit

Permalink
Added unsubscribing from CanExecute-related events when disposing Set…
Browse files Browse the repository at this point in the history
…CommandWithDisposing subscription
  • Loading branch information
Pavel Leonenko committed Jun 27, 2024
1 parent a865cf9 commit 135753b
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 32 deletions.
39 changes: 27 additions & 12 deletions Softeq.XToolkit.Bindings.Droid/DroidBindingFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -147,18 +148,20 @@ public override Delegate GetCommandHandler<T>(
}

/// <inheritdoc />
public override void HandleCommandCanExecute<T>(
public override IDisposable HandleCommandCanExecute<T>(
object element,
ICommand command,
Binding<T, T>? commandParameterBinding)
{
if (element is View view)
{
HandleViewEnabled(view, command, commandParameterBinding);
return HandleViewEnabled(view, command, commandParameterBinding);
}

return Disposable.Create(() => { });
}

private static void HandleViewEnabled<T>(
private static IDisposable HandleViewEnabled<T>(
View view,
ICommand command,
Binding<T, T>? commandParameterBinding)
Expand All @@ -171,20 +174,32 @@ private static void HandleViewEnabled<T>(
() => 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));
}
}
}
Expand Down
39 changes: 27 additions & 12 deletions Softeq.XToolkit.Bindings.iOS/AppleBindingFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -98,18 +99,20 @@ public override Binding<TSource, TTarget> CreateBinding<TSource, TTarget>(
}

/// <inheritdoc />
public override void HandleCommandCanExecute<T>(
public override IDisposable HandleCommandCanExecute<T>(
object element,
ICommand command,
Binding<T, T>? commandParameterBinding)
{
if (element is UIControl control)
{
HandleControlEnabled(control, command, commandParameterBinding);
return HandleControlEnabled(control, command, commandParameterBinding);
}

return Disposable.Create(() => { });
}

private static void HandleControlEnabled<T>(
private static IDisposable HandleControlEnabled<T>(
UIControl control,
ICommand command,
Binding<T, T>? commandParameterBinding)
Expand All @@ -122,20 +125,32 @@ private static void HandleControlEnabled<T>(
() => 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));
}
}
}
Expand Down
16 changes: 10 additions & 6 deletions Softeq.XToolkit.Bindings/BindingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}

/// <inheritdoc cref="SetCommand(object,string,ICommand)" />
Expand Down Expand Up @@ -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<object>(element, command);
return HandleCommandCanExecute<object>(element, command);
}

private static void HandleCommandCanExecute<T>(
private static IDisposable HandleCommandCanExecute<T>(
object element,
ICommand command,
Binding<T, T>? commandParameterBinding = null)
{
BindingFactory.HandleCommandCanExecute(element, command, commandParameterBinding);
return BindingFactory.HandleCommandCanExecute(element, command, commandParameterBinding);
}
}
}
2 changes: 1 addition & 1 deletion Softeq.XToolkit.Bindings/BindingFactoryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public abstract Binding<TSource, TTarget> CreateBinding<TSource, TTarget>(
public abstract string? GetDefaultEventNameForControl(Type type);

/// <inheritdoc />
public abstract void HandleCommandCanExecute<T>(
public abstract IDisposable HandleCommandCanExecute<T>(
object element,
ICommand command,
Binding<T, T>? commandParameterBinding);
Expand Down
3 changes: 2 additions & 1 deletion Softeq.XToolkit.Bindings/IBindingFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ Delegate GetCommandHandler<T, TEventArgs>(
/// that will passed to the <see cref="T:System.Windows.Input.ICommand"/>. Used to determine new value of CanExecute.
/// </param>
/// <typeparam name="T">Type of command parameter.</typeparam>
void HandleCommandCanExecute<T>(object element, ICommand command, Binding<T, T>? commandParameterBinding);
/// <returns>Disposable subscriptions to CanExecute related events.</returns>
IDisposable HandleCommandCanExecute<T>(object element, ICommand command, Binding<T, T>? commandParameterBinding);
}
}

0 comments on commit 135753b

Please sign in to comment.