Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small refactoring and Cancellable commands added #20

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 4 additions & 54 deletions src/Nito.Mvvm.Async/AsyncCommand.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;

Expand All @@ -8,22 +7,16 @@ namespace Nito.Mvvm
/// <summary>
/// A basic asynchronous command, which (by default) is disabled while the command is executing.
/// </summary>
public sealed class AsyncCommand : AsyncCommandBase, INotifyPropertyChanged
public class AsyncCommand : AsyncCommandBaseExtended
{
/// <summary>
/// The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.
/// </summary>
private readonly Func<object, Task> _executeAsync;

/// <summary>
/// Creates a new asynchronous command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
/// <param name="canExecuteChangedFactory">The factory for the implementation of <see cref="ICommand.CanExecuteChanged"/>.</param>
public AsyncCommand(Func<object, Task> executeAsync, Func<object, ICanExecuteChanged> canExecuteChangedFactory)
: base(canExecuteChangedFactory)
: base(executeAsync, canExecuteChangedFactory)
{
_executeAsync = executeAsync;
}

/// <summary>
Expand Down Expand Up @@ -55,57 +48,14 @@ public AsyncCommand(Func<Task> executeAsync)
}

/// <summary>
/// Represents the most recent execution of the asynchronous command. Returns <c>null</c> until the first execution of this command.
/// </summary>
public NotifyTask Execution { get; private set; }

/// <summary>
/// Whether the asynchronous command is currently executing.
/// </summary>
public bool IsExecuting
{
get
{
if (Execution == null)
return false;
return Execution.IsNotCompleted;
}
}

/// <summary>
/// Executes the asynchronous command.
/// </summary>
/// <param name="parameter">The parameter for the command.</param>
public override async Task ExecuteAsync(object parameter)
{
var tcs = new TaskCompletionSource<object>();
Execution = NotifyTask.Create(DoExecuteAsync(tcs.Task, _executeAsync, parameter));
OnCanExecuteChanged();
var propertyChanged = PropertyChanged;
propertyChanged?.Invoke(this, PropertyChangedEventArgsCache.Instance.Get("Execution"));
propertyChanged?.Invoke(this, PropertyChangedEventArgsCache.Instance.Get("IsExecuting"));
tcs.SetResult(null);
await Execution.TaskCompleted;
OnCanExecuteChanged();
PropertyChanged?.Invoke(this, PropertyChangedEventArgsCache.Instance.Get("IsExecuting"));
await Execution.Task;
}

/// <summary>
/// Raised when any properties on this instance have changed.
/// Notify about chnaging can execute state
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
protected override void RaiseCanExecuteChanged() => OnCanExecuteChanged();

/// <summary>
/// The implementation of <see cref="ICommand.CanExecute(object)"/>. Returns <c>false</c> whenever the async command is in progress.
/// </summary>
/// <param name="parameter">The parameter for the command.</param>
protected override bool CanExecute(object parameter) => !IsExecuting;

private static async Task DoExecuteAsync(Task precondition, Func<object, Task> executeAsync, object parameter)
{
await precondition;
await executeAsync(parameter);
}
}
}
101 changes: 101 additions & 0 deletions src/Nito.Mvvm.Async/AsyncCommandBaseExtended.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
namespace Nito.Mvvm
{
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

/// <summary>
/// An async command that is derived from <see cref="AsyncCommandBase"/> and implements common logic for async command classes.
/// </summary>
public abstract class AsyncCommandBaseExtended : AsyncCommandBase, INotifyPropertyChanged
{
/// <summary>
/// The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.
/// </summary>
private Func<object, Task> _executeAsync;

/// <summary>
/// Creates an instance with its own implementation of <see cref="ICommand.CanExecuteChanged"/>.
/// </summary>
protected AsyncCommandBaseExtended(Func<object, Task> executeAsync, Func<object, ICanExecuteChanged> canExecuteChangedFactory)
: base(canExecuteChangedFactory)
{
_executeAsync = executeAsync;
}

/// <summary>
/// Represents the most recent execution of the asynchronous command. Returns <c>null</c> until the first execution of this command.
/// </summary>
public NotifyTask Execution { get; private set; }

/// <summary>
/// Whether the asynchronous command is currently executing.
/// </summary>
public bool IsExecuting
{
get
{
if (Execution == null)
return false;
return Execution.IsNotCompleted;
}
}

/// <summary>
/// Setter method for the function which is executing every time when command invoking.
/// </summary>
/// <param name="executeAsync">The function which will be executed next time command will be invoked.</param>
public void SetExecutingFunc(Func<object, Task> executeAsync) => _executeAsync = executeAsync;

/// <summary>
/// Setter method for the function which is executing every time when command invoking.
/// </summary>
/// <param name="executeAsync">The function which will be executed next time command will be invoked.</param>
public void SetExecutingFunc(Func<Task> executeAsync) => _executeAsync = _ => executeAsync();

/// <summary>
/// Executes the asynchronous command.
/// </summary>
/// <param name="parameter">The parameter for the command.</param>
public override async Task ExecuteAsync(object parameter)
{
var tcs = new TaskCompletionSource<object>();
Execution = NotifyTask.Create(DoExecuteAsync(tcs.Task, _executeAsync, parameter));
RaiseCanExecuteChanged();
OnPropertyChanged("Execution");
OnPropertyChanged("IsExecuting");
tcs.SetResult(null);
await Execution.TaskCompleted;
RaiseCanExecuteChanged();
OnPropertyChanged("IsExecuting");
await Execution.Task;
}

/// <summary>
/// Notify about chnaging can execute state
/// </summary>
protected abstract void RaiseCanExecuteChanged();

/// <summary>
/// Raised when any properties on this instance have changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

/// <summary>
/// Helper method for raising <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propName"> property name </param>
protected void OnPropertyChanged(string propName)
{
Volatile.Read(ref PropertyChanged)?.Invoke(this, PropertyChangedEventArgsCache.Instance.Get(propName));
}

private static async Task DoExecuteAsync(Task precondition, Func<object, Task> executeAsync, object parameter)
{
await precondition;
await executeAsync(parameter);
}
}
}
61 changes: 61 additions & 0 deletions src/Nito.Mvvm.Async/CancellableAsyncCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Nito.Mvvm
{
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

/// <summary>
/// A basic asynchronous command with ability to cancel itself, which (by default) is disabled while the command is executing.
/// </summary>
public class CancellableAsyncCommand : AsyncCommand, ICancellableAsyncCommand
{
/// <summary>
/// Command to cancel corresponding <see cref="CancellableAsyncCommand"/>
/// </summary>
public CancelCommand CancelCommand { get; } = new CancelCommand();

/// <summary>
/// Method for cancelling corresponding <see cref="CancellableAsyncCommand"/>
/// </summary>
public void Cancel() => CancelCommand.Cancel();

/// <summary>
/// Creates a new asynchronous cancellable command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
/// <param name="canExecuteChangedFactory">The factory for the implementation of <see cref="ICommand.CanExecuteChanged"/>.</param>
public CancellableAsyncCommand(
Func<object, CancellationToken, Task> executeAsync,
Func<object, ICanExecuteChanged> canExecuteChangedFactory)
: base(_ => TaskExt.CompletedTask, canExecuteChangedFactory) =>
SetExecutingFunc(CancelCommand.Wrap(executeAsync));

/// <summary>
/// Creates a new asynchronous cancellable command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
public CancellableAsyncCommand(Func<object, CancellationToken, Task> executeAsync)
: base(_ => TaskExt.CompletedTask) =>
SetExecutingFunc(CancelCommand.Wrap(executeAsync));

/// <summary>
/// Creates a new asynchronous cancellable command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
/// <param name="canExecuteChangedFactory">The factory for the implementation of <see cref="ICommand.CanExecuteChanged"/>.</param>
public CancellableAsyncCommand(
Func<CancellationToken, Task> executeAsync,
Func<object, ICanExecuteChanged> canExecuteChangedFactory)
: base(() => TaskExt.CompletedTask, canExecuteChangedFactory) =>
SetExecutingFunc(CancelCommand.Wrap(executeAsync));

/// <summary>
/// Creates a new asynchronous cancellable command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
public CancellableAsyncCommand(Func<CancellationToken, Task> executeAsync)
: base(() => TaskExt.CompletedTask) =>
SetExecutingFunc(CancelCommand.Wrap(executeAsync));
}
}
69 changes: 69 additions & 0 deletions src/Nito.Mvvm.Async/CancellableAsyncCommandCustom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace Nito.Mvvm
{
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

/// <summary>
/// An asynchronous command with ability to cancel itself where the user determines when it can execute.
/// </summary>
public class CancellableAsyncCommandCustom : CustomAsyncCommand, ICancellableAsyncCommand
{
/// <summary>
/// Command to cancel corresponding <see cref="CancellableAsyncCommandCustom"/>
/// </summary>
public CancelCommand CancelCommand { get; } = new CancelCommand();

/// <summary>
/// Method for cancelling corresponding <see cref="CancellableAsyncCommandCustom"/>
/// </summary>
public void Cancel() => CancelCommand.Cancel();

/// <summary>
/// Creates a new cancellable asynchronous command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
/// <param name="canExecute">The implementation of <see cref="ICommand.CanExecute(object)"/>.</param>
/// <param name="canExecuteChangedFactory">The factory for the implementation of <see cref="ICommand.CanExecuteChanged"/>.</param>
public CancellableAsyncCommandCustom(
Func<object, CancellationToken, Task> executeAsync,
Func<object, bool> canExecute,
Func<object, ICanExecuteChanged> canExecuteChangedFactory)
: base(_ => TaskExt.CompletedTask, canExecute, canExecuteChangedFactory) =>
SetExecutingFunc(CancelCommand.Wrap(executeAsync));

/// <summary>
/// Creates a new cancellable asynchronous command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
/// <param name="canExecute">The implementation of <see cref="ICommand.CanExecute(object)"/>.</param>
public CancellableAsyncCommandCustom(
Func<object, CancellationToken, Task> executeAsync,
Func<object, bool> canExecute)
: base(_ => TaskExt.CompletedTask, canExecute) =>
SetExecutingFunc(CancelCommand.Wrap(executeAsync));

/// <summary>
/// Creates a new cancellable asynchronous command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
/// <param name="canExecute">The implementation of <see cref="ICommand.CanExecute(object)"/>.</param>
/// <param name="canExecuteChangedFactory">The factory for the implementation of <see cref="ICommand.CanExecuteChanged"/>.</param>
public CancellableAsyncCommandCustom(
Func<CancellationToken, Task> executeAsync,
Func<bool> canExecute,
Func<object, ICanExecuteChanged> canExecuteChangedFactory)
: base(() => TaskExt.CompletedTask, canExecute, canExecuteChangedFactory) =>
SetExecutingFunc(CancelCommand.Wrap(executeAsync));

/// <summary>
/// Creates a new cancellable asynchronous command, with the specified asynchronous delegate as its implementation.
/// </summary>
/// <param name="executeAsync">The implementation of <see cref="IAsyncCommand.ExecuteAsync(object)"/>.</param>
/// <param name="canExecute">The implementation of <see cref="ICommand.CanExecute(object)"/>.</param>
public CancellableAsyncCommandCustom(Func<CancellationToken, Task> executeAsync, Func<bool> canExecute)
: base(() => TaskExt.CompletedTask, canExecute) =>
SetExecutingFunc(CancelCommand.Wrap(executeAsync));
}
}
Loading