From 55a17137025604ab6dbfa6ab172e6c08d35522f5 Mon Sep 17 00:00:00 2001 From: Mahdi Hosseini Date: Mon, 24 Feb 2025 16:55:21 +0330 Subject: [PATCH] Add DelegateCommand --- README.md | 1 + .../Common/Command/DelegateCommand.cs | 76 +++++++++++++++++++ .../DelegateCommand/AsyncDelegateCommand.cs | 61 +++++++++++++++ .../DelegateCommand/IDelegateCommand.cs | 10 +++ .../DelegateCommand/SyncDelegateCommand.cs | 41 ++++++++++ 5 files changed, 189 insertions(+) create mode 100644 dev/DevWinUI/Common/Command/DelegateCommand.cs create mode 100644 dev/DevWinUI/Common/Command/Internals/DelegateCommand/AsyncDelegateCommand.cs create mode 100644 dev/DevWinUI/Common/Command/Internals/DelegateCommand/IDelegateCommand.cs create mode 100644 dev/DevWinUI/Common/Command/Internals/DelegateCommand/SyncDelegateCommand.cs diff --git a/README.md b/README.md index 975b710..fd1a48f 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ If you encounter any issues or have feedback, please report them [here](https:// ## 🔥 DevWinUI 🔥 ### ⚡ What’s Inside? ⚡ + - ✨ DelegateCommand - ✨ RichTextFormatter - ✨ Converter - ✨ Extensions diff --git a/dev/DevWinUI/Common/Command/DelegateCommand.cs b/dev/DevWinUI/Common/Command/DelegateCommand.cs new file mode 100644 index 0000000..f677625 --- /dev/null +++ b/dev/DevWinUI/Common/Command/DelegateCommand.cs @@ -0,0 +1,76 @@ +namespace DevWinUI; + +public static partial class DelegateCommand +{ + public static IDelegateCommand Create(Action? execute) + { + return new SyncDelegateCommand(WrapAction(execute), CanExecuteTrue); + } + + public static IDelegateCommand Create(Action? execute) + { + return new SyncDelegateCommand(execute ?? DefaultExecute, CanExecuteTrue); + } + + public static IDelegateCommand Create(Action? execute, Func? canExecute) + { + return new SyncDelegateCommand(WrapAction(execute), WrapAction(canExecute)); + } + + public static IDelegateCommand Create(Action? execute, Func? canExecute) + { + return new SyncDelegateCommand(execute ?? DefaultExecute, canExecute ?? CanExecuteTrue); + } + + public static IDelegateCommand Create(Func? execute) + { + return new AsyncDelegateCommand(WrapAction(execute), CanExecuteTrue); + } + + public static IDelegateCommand Create(Func? execute) + { + return new AsyncDelegateCommand(execute ?? DefaultExecuteAsync, CanExecuteTrue); + } + + public static IDelegateCommand Create(Func? execute, Func? canExecute) + { + return new AsyncDelegateCommand(WrapAction(execute), WrapAction(canExecute)); + } + + public static IDelegateCommand Create(Func? execute, Func? canExecute) + { + return new AsyncDelegateCommand(execute ?? DefaultExecuteAsync, canExecute ?? CanExecuteTrue); + } + + private static void DefaultExecute(object? _) + { + } + + private static Task DefaultExecuteAsync(object? _) => Task.CompletedTask; + + private static bool CanExecuteTrue(object? _) => true; + + private static Func WrapAction(Func? action) + { + if (action is null) + return DefaultExecuteAsync; + + return _ => action(); + } + + private static Action WrapAction(Action? action) + { + if (action is null) + return DefaultExecute; + + return _ => action(); + } + + private static Func WrapAction(Func? action) + { + if (action is null) + return CanExecuteTrue; + + return _ => action(); + } +} diff --git a/dev/DevWinUI/Common/Command/Internals/DelegateCommand/AsyncDelegateCommand.cs b/dev/DevWinUI/Common/Command/Internals/DelegateCommand/AsyncDelegateCommand.cs new file mode 100644 index 0000000..9596407 --- /dev/null +++ b/dev/DevWinUI/Common/Command/Internals/DelegateCommand/AsyncDelegateCommand.cs @@ -0,0 +1,61 @@ +using Microsoft.UI.Dispatching; +using System.Diagnostics.CodeAnalysis; + +namespace DevWinUI; + +internal sealed partial class AsyncDelegateCommand : IDelegateCommand +{ + private readonly Func _execute; + private readonly Func _canExecute; + private readonly DispatcherQueue _dispatcher; + private bool _isExecuting; + + public event EventHandler? CanExecuteChanged; + + public AsyncDelegateCommand(Func execute, Func canExecute) + { + _execute = execute; + _canExecute = canExecute; + _dispatcher = DispatcherQueue.GetForCurrentThread(); + } + + public bool CanExecute(object? parameter) + { + return !_isExecuting && _canExecute.Invoke(parameter); + } + + [SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "Must be void")] + public async void Execute(object? parameter) + { + if (_isExecuting) + return; + + try + { + _isExecuting = true; + RaiseCanExecuteChanged(); + await _execute.Invoke(parameter); + } + finally + { + _isExecuting = false; + RaiseCanExecuteChanged(); + } + } + + public void RaiseCanExecuteChanged() + { + var canExecuteChanged = CanExecuteChanged; + if (canExecuteChanged is not null) + { + if (_dispatcher is not null) + { + _dispatcher.TryEnqueue(() => canExecuteChanged.Invoke(this, EventArgs.Empty)); + } + else + { + canExecuteChanged.Invoke(this, EventArgs.Empty); + } + } + } +} diff --git a/dev/DevWinUI/Common/Command/Internals/DelegateCommand/IDelegateCommand.cs b/dev/DevWinUI/Common/Command/Internals/DelegateCommand/IDelegateCommand.cs new file mode 100644 index 0000000..9bafcd1 --- /dev/null +++ b/dev/DevWinUI/Common/Command/Internals/DelegateCommand/IDelegateCommand.cs @@ -0,0 +1,10 @@ +using System.Diagnostics.CodeAnalysis; +using System.Windows.Input; + +namespace DevWinUI; + +public interface IDelegateCommand : ICommand +{ + [SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "This method raise an existing event")] + void RaiseCanExecuteChanged(); +} diff --git a/dev/DevWinUI/Common/Command/Internals/DelegateCommand/SyncDelegateCommand.cs b/dev/DevWinUI/Common/Command/Internals/DelegateCommand/SyncDelegateCommand.cs new file mode 100644 index 0000000..a69c47f --- /dev/null +++ b/dev/DevWinUI/Common/Command/Internals/DelegateCommand/SyncDelegateCommand.cs @@ -0,0 +1,41 @@ +using Microsoft.UI.Dispatching; + +namespace DevWinUI; + +internal sealed partial class SyncDelegateCommand : IDelegateCommand +{ + private readonly Action _execute; + private readonly Func _canExecute; + private readonly DispatcherQueue _dispatcher; + + public event EventHandler? CanExecuteChanged; + + public SyncDelegateCommand(Action execute, Func canExecute) + { + _execute = execute; + _canExecute = canExecute; + _dispatcher = DispatcherQueue.GetForCurrentThread(); + } + + public bool CanExecute(object? parameter) + { + return _canExecute.Invoke(parameter); + } + + public void Execute(object? parameter) + { + _execute.Invoke(parameter); + } + + public void RaiseCanExecuteChanged() + { + if (_dispatcher is not null) + { + _dispatcher.TryEnqueue(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty)); + } + else + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } +}