using System.Linq.Expressions; using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; using System.Reflection.Emit; using System.Resources; namespace WxOrder.Framework.Commons { /// /// 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); } } }