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 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; } } } }