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