Trigger a Command
CommunityToolkit.Mvvm
uses RelayCommand
to generate a field and property to represents the command for certain method.
Code Emission
[RelayCommand]
private void RemoveItem(ToDoItemViewModel item)
{
ToDoItems.Remove(item);
}
2
3
4
5
Generated part as following:
- A field of type
RelayCommand
(not theRelayCommandAttribute
) - A property for the generated field typed as
IRelayCommand
.
/// <summary>The backing field for <see cref="RemoveItemCommand"/>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.3.0.0")]
private global::CommunityToolkit.Mvvm.Input.RelayCommand<global::ToDoList.ViewModels.ToDoItemViewModel>? removeItemCommand; // [!code highlight]
/// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand{T}"/> instance wrapping <see cref="RemoveItem"/>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.3.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public global::CommunityToolkit.Mvvm.Input.IRelayCommand<global::ToDoList.ViewModels.ToDoItemViewModel> RemoveItemCommand => removeItemCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand<global::ToDoList.ViewModels.ToDoItemViewModel>(new global::System.Action<global::ToDoList.ViewModels.ToDoItemViewModel>(RemoveItem)); // [!code highlight]
2
3
4
5
6
7
Behind the RelayCommand
To understand how RelayCommand
works, we should go back to the story of ICommand
first.
ICommand
is a builtin interface in .NET, has three parts to describe the command pattern:
Execute
: A real action for the command.CanExecute
: Telling whether the command can be executed now.CanExecuteChanged
: Informing whenCanExecute
evaluation changed.
public interface ICommand
{
/// <summary>Occurs when changes occur that affect whether or not the command should execute.</summary>
event EventHandler? CanExecuteChanged;
/// <summary>Defines the method that determines whether the command can execute in its current state.</summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
/// <returns>
/// <see langword="true" /> if this command can be executed; otherwise, <see langword="false" />.</returns>
bool CanExecute(object? parameter);
/// <summary>Defines the method to be called when the command is invoked.</summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
void Execute(object? parameter);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
IRelayCommand
in CommunityToolkit
simply extends the ICommand
with a new notify method. We do need a real implementation of how we inform on CanExecuteChanged
, this is generally missing in .NET builtin event-driven interfaces.
public interface IRelayCommand : ICommand
{
/// <summary>
/// Notifies that the <see cref="ICommand.CanExecute"/> property has changed.
/// </summary>
void NotifyCanExecuteChanged();
}
2
3
4
5
6
7
NOTE
IRelayCommand
and RelayCommand
have generic versions and async versions.
The type of generated field is RelayCommand
which implements the IRelayCommand
, this is essentially a base wrapper for the logic behind.
public sealed partial class RelayCommand : IRelayCommand
{
/// <summary>
/// The <see cref="Action"/> to invoke when <see cref="Execute"/> is used.
/// </summary>
private readonly Action execute;
/// <summary>
/// The optional action to invoke when <see cref="CanExecute"/> is used.
/// </summary>
private readonly Func<bool>? canExecute;
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="execute"/> is <see langword="null"/>.</exception>
public RelayCommand(Action execute)
{
ArgumentNullException.ThrowIfNull(execute);
this.execute = execute;
}
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="execute"/> or <paramref name="canExecute"/> are <see langword="null"/>.</exception>
public RelayCommand(Action execute, Func<bool> canExecute)
{
ArgumentNullException.ThrowIfNull(execute);
ArgumentNullException.ThrowIfNull(canExecute);
this.execute = execute;
this.canExecute = canExecute;
}
/// <inheritdoc/>
public void NotifyCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(object? parameter)
{
return this.canExecute?.Invoke() != false;
}
/// <inheritdoc/>
public void Execute(object? parameter)
{
this.execute();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61