1
0
mirror of https://github.com/chylex/.NET-Community-Toolkit.git synced 2025-08-08 20:40:36 +02:00

Add IAsyncRelayCommandExtensions.CreateCancelCommand

This commit is contained in:
Sergio Pedri 2022-03-01 17:08:02 +01:00
parent 3fc61fe03d
commit 8ae1006b4d
6 changed files with 160 additions and 2 deletions

View File

@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel.__Internals;
using CommunityToolkit.Mvvm.Input.Internals;
#pragma warning disable CS0618
@ -19,7 +20,7 @@ namespace CommunityToolkit.Mvvm.Input;
/// action, and providing an <see cref="ExecutionTask"/> property that notifies changes when
/// <see cref="ExecuteAsync"/> is invoked and when the returned <see cref="Task"/> completes.
/// </summary>
public sealed class AsyncRelayCommand : IAsyncRelayCommand
public sealed class AsyncRelayCommand : IAsyncRelayCommand, ICancellationAwareCommand
{
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="ExecutionTask"/>.
@ -252,6 +253,9 @@ static async void MonitorTask(AsyncRelayCommand @this, Task task)
/// <inheritdoc/>
public bool IsRunning => ExecutionTask is { IsCompleted: false };
/// <inheritdoc/>
bool ICancellationAwareCommand.IsCancellationSupported => this.execute is null;
/// <inheritdoc/>
public void NotifyCanExecuteChanged()
{

View File

@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel.__Internals;
using CommunityToolkit.Mvvm.Input.Internals;
#pragma warning disable CS0618
@ -17,7 +18,7 @@ namespace CommunityToolkit.Mvvm.Input;
/// A generic command that provides a more specific version of <see cref="AsyncRelayCommand"/>.
/// </summary>
/// <typeparam name="T">The type of parameter being passed as input to the callbacks.</typeparam>
public sealed class AsyncRelayCommand<T> : IAsyncRelayCommand<T>
public sealed class AsyncRelayCommand<T> : IAsyncRelayCommand<T>, ICancellationAwareCommand
{
/// <summary>
/// The <see cref="Func{TResult}"/> to invoke when <see cref="Execute(T)"/> is used.
@ -234,6 +235,9 @@ static async void MonitorTask(AsyncRelayCommand<T> @this, Task task)
/// <inheritdoc/>
public bool IsRunning => ExecutionTask is { IsCompleted: false };
/// <inheritdoc/>
bool ICancellationAwareCommand.IsCancellationSupported => this.execute is null;
/// <inheritdoc/>
public void NotifyCanExecuteChanged()
{

View File

@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input.Internals;
namespace CommunityToolkit.Mvvm.Input;
/// <summary>
/// Extensions for the <see cref="IAsyncRelayCommand"/> type.
/// </summary>
public static class IAsyncRelayCommandExtensions
{
/// <summary>
/// Creates an <see cref="ICommand"/> instance that can be used to cancel execution on the input command.
/// The returned command will also notify when it can be executed based on the state of the wrapped command.
/// </summary>
/// <param name="command">The input <see cref="IAsyncRelayCommand"/> instance to create a cancellation command for.</param>
/// <returns>An <see cref="ICommand"/> instance that can be used to monitor and signal cancellation for <paramref name="command"/>.</returns>
/// <remarks>The reeturned instance is not guaranteed to be unique across multiple invocations with the same arguments.</remarks>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="command"/> is <see langword="null"/>.</exception>
public static ICommand CreateCancelCommand(this IAsyncRelayCommand command)
{
ArgumentNullException.ThrowIfNull(command);
// If the command is known not to ever allow cancellation, just reuse the same instance
if (command is ICancellationAwareCommand { IsCancellationSupported: false })
{
return DisabledCommand.Instance;
}
// Create a new cancel command wrapping the input one
return new CancelCommand(command);
}
}

View File

@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace CommunityToolkit.Mvvm.Input.Internals;
/// <summary>
/// A <see cref="ICommand"/> implementation wrapping <see cref="IAsyncRelayCommand"/> to support cancellation.
/// </summary>
internal sealed class CancelCommand : ICommand
{
/// <summary>
/// The wrapped <see cref="IAsyncRelayCommand"/> instance.
/// </summary>
private readonly IAsyncRelayCommand command;
/// <summary>
/// Creates a new <see cref="CancelCommand"/> instance.
/// </summary>
/// <param name="command">The <see cref="IAsyncRelayCommand"/> instance to wrap.</param>
public CancelCommand(IAsyncRelayCommand command)
{
this.command = command;
this.command.PropertyChanged += OnPropertyChanged;
}
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
/// <inheritdoc/>
public bool CanExecute(object? parameter)
{
return this.command.CanBeCanceled;
}
/// <inheritdoc/>
public void Execute(object? parameter)
{
this.command.Cancel();
}
/// <inheritdoc cref="PropertyChangedEventHandler"/>
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is null or nameof(IAsyncRelayCommand.CanBeCanceled))
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows.Input;
namespace CommunityToolkit.Mvvm.Input.Internals;
/// <summary>
/// A reusable <see cref="ICommand"/> instance that is always disabled.
/// </summary>
internal sealed class DisabledCommand : ICommand
{
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged
{
add { }
remove { }
}
/// <summary>
/// Gets a shared, reusable <see cref="DisabledCommand"/> instance.
/// </summary>
/// <remarks>
/// This instance can safely be used across multiple objects without having
/// to worry about this static keeping others alive, as the event uses a
/// custom accessor that just discards items (as the event is known to never
/// be raised). As such, this instance will never act as root for other objects.
/// </remarks>
public static DisabledCommand Instance { get; } = new();
/// <inheritdoc/>
public bool CanExecute(object? parameter)
{
return false;
}
/// <inheritdoc/>
public void Execute(object? parameter)
{
}
}

View File

@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace CommunityToolkit.Mvvm.Input.Internals;
/// <summary>
/// An interface for commands that know whether they support cancellation or not.
/// </summary>
internal interface ICancellationAwareCommand
{
/// <summary>
/// Gets whether or not the current command supports cancellation.
/// </summary>
bool IsCancellationSupported { get; }
}