Goals
- I want to be able to call async code using the MVVM pattern
- I want to be able to add loading animations/screens without having to add properties for each command on my viewmodels
- I may want to be able to cancel these operations
This is what I came up with
public abstract class CommandBase : ICommand
{
private readonly Func<bool> _canExecute;
public CommandBase()
{
}
public CommandBase(Func<bool> canExecute)
{
if (canExecute == null) throw new ArgumentNullException(nameof(canExecute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public abstract void Execute(object parameter);
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public event EventHandler CanExecuteChanged;
}
public class AsyncCommand : CommandBase, INotifyPropertyChanged
{
private readonly Func<CancellationToken, Task> _action;
private CancellationTokenSource _cancellationTokenSource;
private bool _isRunning;
public bool IsRunning
{
get { return _isRunning; }
set
{
_isRunning = value;
OnPropertyChanged();
}
}
private ICommand _cancelCommand;
public ICommand CancelCommand => _cancelCommand ?? (_cancelCommand = new RelayCommand(Cancel));
public AsyncCommand(Func<CancellationToken, Task> action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
_action = action;
}
public AsyncCommand(Func<CancellationToken, Task> action, Func<bool> canExecute) : base(canExecute)
{
if (action == null) throw new ArgumentNullException(nameof(action));
_action = action;
}
private void Cancel()
{
_cancellationTokenSource?.Cancel();
}
public override async void Execute(object parameter)
{
IsRunning = true;
try
{
using (var tokenSource = new CancellationTokenSource())
{
_cancellationTokenSource = tokenSource;
await ExecuteAsync(tokenSource.Token);
}
}
finally
{
_cancellationTokenSource = null;
IsRunning = false;
}
}
private Task ExecuteAsync(CancellationToken cancellationToken)
{
return _action(cancellationToken);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
It can be used like this:
<Button Content="Start" Command="{Binding UpdateDisplayTextCommand}"/>
<Button Content="Cancel" Command="{Binding UpdateDisplayTextCommand.CancelCommand}"/>
<ProgressBar IsIndeterminate="{Binding UpdateDisplayTextCommand.IsRunning}" />
Any faults on this implementation? I'm also open to ideas on features which could be added to this class.