From 751b0d43e8fb276a23eeb0e3ee79d5b101b97070 Mon Sep 17 00:00:00 2001 From: chfei Date: Fri, 14 Aug 2026 15:57:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BC=82=E6=AD=A5Command?= =?UTF-8?q?=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 29WxOrderWpf/Common/AsyncDelegateCommand.cs | 245 +++++++++++++++ .../Common/AsyncDelegateCommand{T}.cs | 285 ++++++++++++++++++ 29WxOrderWpf/Common/IAsyncCommand.cs | 28 ++ 3 files changed, 558 insertions(+) create mode 100644 29WxOrderWpf/Common/AsyncDelegateCommand.cs create mode 100644 29WxOrderWpf/Common/AsyncDelegateCommand{T}.cs create mode 100644 29WxOrderWpf/Common/IAsyncCommand.cs diff --git a/29WxOrderWpf/Common/AsyncDelegateCommand.cs b/29WxOrderWpf/Common/AsyncDelegateCommand.cs new file mode 100644 index 0000000..993112b --- /dev/null +++ b/29WxOrderWpf/Common/AsyncDelegateCommand.cs @@ -0,0 +1,245 @@ +using System.Linq.Expressions; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Reflection.Emit; +using System.Resources; +using _29WxOrderWpf.Commands; + + +namespace _29WxOrderWpf.Common +{ + + /// + /// Provides an implementation of the + /// + public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand + { + private bool _enableParallelExecution = false; + private bool _isExecuting = false; + private readonly Func _executeMethod; + private Func _canExecuteMethod; + private Func _getCancellationToken = () => CancellationToken.None; + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) +#if NET6_0_OR_GREATER + : this (c => executeMethod().WaitAsync(c), () => true) +#else + : this(c => executeMethod(), () => true) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) + : this(executeMethod, () => true) + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) +#if NET6_0_OR_GREATER + : this(c => executeMethod().WaitAsync(c), canExecuteMethod) +#else + : this(c => executeMethod(), canExecuteMethod) +#endif + { + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "Neither the executeMethod nor the canExecuteMethod delegates can be null"); + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + /// Gets the current state of the AsyncDelegateCommand + /// + public bool IsExecuting + { + get => _isExecuting; + private set + { + _isExecuting = value; + OnCanExecuteChanged(); + } + } + + /// + /// Executes the command. + /// + public async Task Execute(CancellationToken? cancellationToken = null) + { + var token = cancellationToken ?? _getCancellationToken(); + try + { + if (!_enableParallelExecution && IsExecuting) + return; + + IsExecuting = true; + await _executeMethod(token) + .ConfigureAwait(false); + } + catch + { + throw; + + } + finally + { + IsExecuting = false; + } + } + + /// + /// Determines if the command can be executed. + /// + /// Returns if the command can execute,otherwise returns . + public bool CanExecute() + { + try + { + if (!_enableParallelExecution && IsExecuting) + return false; + + return _canExecuteMethod?.Invoke() ?? true; + } + catch (Exception ex) + { + return false; + } + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override async void Execute(object parameter) + { + // We don't want to wrap this in a try/catch because we already handle + // or mean to rethrow the exception in the call with the CancellationToken. + var cancellationToken = _getCancellationToken(); + await Execute(cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + return CanExecute(); + } + + /// + /// Enables Parallel Execution of Async Tasks + /// + /// The current instance of . + public AsyncDelegateCommand EnableParallelExecution() + { + _enableParallelExecution = true; + return this; + } + + /// + /// Sets the based on the specified timeout. + /// + /// A specified timeout. + /// The current instance of . + public AsyncDelegateCommand CancelAfter(TimeSpan timeout) => + CancellationTokenSourceFactory(() => new CancellationTokenSource(timeout).Token); + + /// + /// Provides a delegate callback to provide a default CancellationToken when the Command is invoked. + /// + /// The default Factory. + /// The current instance of . + public AsyncDelegateCommand CancellationTokenSourceFactory(Func factory) + { + _getCancellationToken = factory; + return this; + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The object type containing the property specified in the expression. + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + _canExecuteMethod = canExecuteExpression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// TThe callback when a specific exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + where TException : Exception + { + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// The generic / default callback when an exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + { + return this; + } + + Task IAsyncCommand.ExecuteAsync(object parameter) + { + return Execute(_getCancellationToken()); + } + + Task IAsyncCommand.ExecuteAsync(object parameter, CancellationToken cancellationToken) + { + return Execute(cancellationToken); + } + } +} \ No newline at end of file diff --git a/29WxOrderWpf/Common/AsyncDelegateCommand{T}.cs b/29WxOrderWpf/Common/AsyncDelegateCommand{T}.cs new file mode 100644 index 0000000..6595c82 --- /dev/null +++ b/29WxOrderWpf/Common/AsyncDelegateCommand{T}.cs @@ -0,0 +1,285 @@ +using System.Linq.Expressions; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Reflection.Emit; +using System.Resources; +using _29WxOrderWpf.Commands; + +namespace _29WxOrderWpf.Common +{ + /// + /// Provides an implementation of the with a generic parameter type. + /// + /// + public class AsyncDelegateCommand : DelegateCommandBase, IAsyncCommand + { + private bool _enableParallelExecution = false; + private bool _isExecuting = false; + private readonly Func _executeMethod; + private Func _canExecuteMethod; + private Func _getCancellationToken = () => CancellationToken.None; + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) +#if NET6_0_OR_GREATER + : this((p,t) => executeMethod(p).WaitAsync(t), _ => true) +#else + : this((p, t) => executeMethod(p), _ => true) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public AsyncDelegateCommand(Func executeMethod) + : this(executeMethod, _ => true) + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) +#if NET6_0_OR_GREATER + : this((p, c) => executeMethod(p).WaitAsync(c), canExecuteMethod) +#else + : this((p, c) => executeMethod(p), canExecuteMethod) +#endif + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The delegate to invoke when is called + public AsyncDelegateCommand(Func executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "Neither the executeMethod nor the canExecuteMethod delegates can be null"); + + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + /// Gets the current state of the AsyncDelegateCommand + /// + public bool IsExecuting + { + get => _isExecuting; + private set + { + _isExecuting = value; + OnCanExecuteChanged(); + } + } + + /// + /// Executes the command. + /// + public async Task Execute(T parameter, CancellationToken? cancellationToken = null) + { + var token = cancellationToken ?? _getCancellationToken(); + + try + { + if (!_enableParallelExecution && IsExecuting) + return; + + IsExecuting = true; + await _executeMethod(parameter, token) + .ConfigureAwait(false); + } + catch (Exception ex) + { + throw; + } + finally + { + IsExecuting = false; + } + } + + /// + /// Determines if the command can be executed. + /// + /// Returns if the command can execute,otherwise returns . + public bool CanExecute(T parameter) + { + try + { + if (!_enableParallelExecution && IsExecuting) + return false; + + return _canExecuteMethod?.Invoke(parameter) ?? true; + } + catch (Exception ex) + { + + throw; + + return false; + } + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override async void Execute(object parameter) + { + var cancellationToken = _getCancellationToken(); + T parameterAsT; + try + { + parameterAsT = (T)parameter; + } + catch (Exception ex) + { + throw; + } + + // If we had an exception casting the parameter to T , + // we would have already returned. We want to surface any + // exceptions thrown by the Execute method. + await Execute(parameterAsT, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + try + { + return CanExecute((T)parameter); + } + catch (Exception ex) + { + throw; + } + } + + /// + /// Enables Parallel Execution of Async Tasks + /// + /// The current instance of . + public AsyncDelegateCommand EnableParallelExecution() + { + _enableParallelExecution = true; + return this; + } + + /// + /// Sets the based on the specified timeout. + /// + /// A specified timeout. + /// The current instance of . + public AsyncDelegateCommand CancelAfter(TimeSpan timeout) => + CancellationTokenSourceFactory(() => new CancellationTokenSource(timeout).Token); + + /// + /// Provides a delegate callback to provide a default CancellationToken when the Command is invoked. + /// + /// The default Factory. + /// The current instance of . + public AsyncDelegateCommand CancellationTokenSourceFactory(Func factory) + { + _getCancellationToken = factory; + return this; + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The type of the return value of the method that this delegate encapsulates + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of . + public AsyncDelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + Expression> expression = Expression.Lambda>(canExecuteExpression.Body, Expression.Parameter(typeof(T), "o")); + _canExecuteMethod = expression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// TThe callback when a specific exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + where TException : Exception + { + return this; + } + + /// + /// Provides the ability to connect a delegate to catch exceptions encountered by CanExecute or the Execute methods of the DelegateCommand + /// + /// The generic / default callback when an exception is encountered + /// The current instance of . + public AsyncDelegateCommand Catch(Action @catch) + { + return this; + } + + async Task IAsyncCommand.ExecuteAsync(object parameter) + { + try + { + // If T is not nullable this may throw an exception + await Execute((T)parameter, _getCancellationToken()); + } + catch (Exception ex) + { + throw; + + } + } + + async Task IAsyncCommand.ExecuteAsync(object parameter, CancellationToken cancellationToken) + { + try + { + // If T is not nullable this may throw an exception + await Execute((T)parameter, cancellationToken); + } + catch (Exception ex) + { + throw; + } + } + } +} \ No newline at end of file diff --git a/29WxOrderWpf/Common/IAsyncCommand.cs b/29WxOrderWpf/Common/IAsyncCommand.cs new file mode 100644 index 0000000..d54b7c3 --- /dev/null +++ b/29WxOrderWpf/Common/IAsyncCommand.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace _29WxOrderWpf.Common +{ + + /// + /// Provides an abstraction layer for custom controls which want to make use of Async Commands + /// + public interface IAsyncCommand : ICommand + { + /// + /// Executes the Command with a specified parameter and the Default . + /// + /// The Command Parameter + /// An Asynchronous Task + Task ExecuteAsync(object parameter); + + /// + /// Executes the Command with a specified parameter and using a + /// + /// The Command Parameter + /// The . + /// An Asynchronous Task + Task ExecuteAsync(object parameter, CancellationToken cancellationToken); + } +} \ No newline at end of file