Open sandboxFocusImprove this doc

WPF Command

In WPF, a command is an object that implements the ICommand interface, which can be bound to UI controls such as buttons to trigger actions and can enable or disable these controls based on the CanExecute method. The Execute method runs the command, while the CanExecuteChanged event notifies when the command's availability changes.

Implementing WPF commands manually typically requires much boilerplate code, especially to support the CanExecuteChanged event.

The [Command] aspect generates most of the WPF command boilerplate automatically. When applied to a method, the aspect generates a Command property. It can also bind to a CanExecute property or method and integrates with INotifyPropertyChanged.

Generating a WPF command property from a method

To generate a WPF command property from a method:

  1. Add a reference to the Metalama.Patterns.Wpf package to your project.

  2. Add the [Command] attribute to the method that must be executed when the command is invoked. This method will become the implementation of the ICommand.Execute interface method. It must have one of the following signatures, where T is an arbitrary type:

    [Command]
    void Execute();
    
    [Command( Background = true )]
    void Execute(CancellationToken);  // Only for background commands. See below.
    
    [Command]
    void Execute(T);
    
    [Command( Background = true )]
    void Execute(T, CancellationToken);  // Only for background commands. See below.
    
    [Command]
    Task ExecuteAsync();
    
    [Command]
    Task ExecuteAsync(CancellationToken);
    
    [Command]
    Task ExecuteAsync(T);
    
    [Command]
    Task ExecuteAsync(T, CancellationToken);
    
    
  3. Make the class partial to enable referencing the generated command properties from C# or WPF source code.

Example: Simple commands

The following example implements a window with two commands: Increment and Decrement. As illustrated, the [Command] aspect generates two properties, IncrementCommand and DecrementCommand, assigned to an instance of the DelegateCommand helper class. This class accepts a delegate to the Increment or Decrement method.

Source Code
1using System.Windows;
2using Metalama.Patterns.Wpf;
3
4namespace Doc.Command.SimpleCommand;
5
6public class MyWindow : Window
7{
8    public int Counter { get; private set; }
9
10    [Command]
11    public void Increment()
12    {
13        this.Counter++;
14    }
15
16    [Command]
17    public void Decrement()
18    {
19        this.Counter--;
20    }
21}
Transformed Code
1using System.Windows;
2using Metalama.Patterns.Wpf;
3
4namespace Doc.Command.SimpleCommand;
5
6public class MyWindow : Window
7{
8    public int Counter { get; private set; }
9
10    [Command]
11    public void Increment()
12    {
13        this.Counter++;
14    }
15
16    [Command]
17    public void Decrement()
18    {
19        this.Counter--;
20    }
21
22    public MyWindow()
23    {
24        IncrementCommand = DelegateCommandFactory.CreateDelegateCommand(Increment, null);
25        DecrementCommand = DelegateCommandFactory.CreateDelegateCommand(Decrement, null);
26    }
27
28    public DelegateCommand DecrementCommand { get; }
29
30    public DelegateCommand IncrementCommand { get; }
31}

Adding a CanExecute method or property

In addition to the Execute method, you can also supply an implementation of ICommand.CanExecute. This implementation can be either a bool property or, when the Execute method has a parameter, a method that accepts the same parameter type and returns bool.

There are two ways to associate a CanExecute implementation with the Execute member:

  • Implicitly, by respecting naming conventions. For a command named Foo, the CanExecute member can be named CanFoo, CanExecuteFoo, or IsFooEnabled. See below to learn how to customize these naming conventions.
  • Explicitly, by setting the CanExecuteMethod or CanExecuteProperty property of the CommandAttribute.

When the CanExecute member is a property and the declaring type implements the INotifyPropertyChanged interface, the ICommand.CanExecuteChanged event will be raised whenever the CanExecute property changes. You can use the [Observable] aspect to implement INotifyPropertyChanged. See Metalama.Patterns.Observability for details.

Example: Commands with a CanExecute property and implicit association

The following example demonstrates two commands, Increment and Decrement, coupled to properties that determine if these commands are available: CanIncrement and CanDecrement.

Source Code
1using System.Windows;
2using Metalama.Patterns.Wpf;
3
4namespace Doc.Command.CanExecute;
5
6public class MyWindow : Window
7{
8    public int Counter { get; private set; }
9
10    public bool CanExecuteIncrement => this.Counter < 10;
11
12    public bool CanExecuteDecrement => this.Counter > 0;
13
14    [Command]
15    public void Increment()
16    {
17        this.Counter++;
18    }
19
20    [Command]
21    public void Decrement()
22    {
23        this.Counter--;
24    }
25}
Transformed Code
1using System.Windows;
2using Metalama.Patterns.Wpf;
3
4namespace Doc.Command.CanExecute;
5
6public class MyWindow : Window
7{
8    public int Counter { get; private set; }
9
10    public bool CanExecuteIncrement => this.Counter < 10;
11
12    public bool CanExecuteDecrement => this.Counter > 0;
13
14    [Command]
15    public void Increment()
16    {
17        this.Counter++;
18    }
19
20    [Command]
21    public void Decrement()
22    {
23        this.Counter--;
24    }
25
26    public MyWindow()
27    {
28        IncrementCommand = DelegateCommandFactory.CreateDelegateCommand(Increment, () => CanExecuteIncrement);
29        DecrementCommand = DelegateCommandFactory.CreateDelegateCommand(Decrement, () => CanExecuteDecrement);
30    }
31
32    public DelegateCommand DecrementCommand { get; }
33
34    public DelegateCommand IncrementCommand { get; }
35}

Example: Commands with a CanExecute property and explicit association

This example is identical to the one above, but it uses the CanExecuteProperty property to explicitly associate the CanExecute property with their Execute method.

Source Code
1using System.Windows;
2using Metalama.Patterns.Wpf;
3
4namespace Doc.Command.CanExecute_Explicit;
5
6public class MyWindow : Window
7{
8    public int Counter { get; private set; }
9
10    public bool CanExecuteIncrement => this.Counter < 10;
11
12    public bool CanExecuteDecrement => this.Counter > 0;
13
14    [Command( CanExecuteProperty = nameof(CanExecuteIncrement) )]
15    public void Increment()
16    {
17        this.Counter++;
18    }
19
20    [Command( CanExecuteProperty = nameof(CanExecuteDecrement) )]
21    public void Decrement()
22    {
23        this.Counter--;
24    }
25}
Transformed Code
1using System.Windows;
2using Metalama.Patterns.Wpf;
3
4namespace Doc.Command.CanExecute_Explicit;
5
6public class MyWindow : Window
7{
8    public int Counter { get; private set; }
9
10    public bool CanExecuteIncrement => this.Counter < 10;
11
12    public bool CanExecuteDecrement => this.Counter > 0;
13
14    [Command(CanExecuteProperty = nameof(CanExecuteIncrement))]
15    public void Increment()
16    {
17        this.Counter++;
18    }
19
20    [Command(CanExecuteProperty = nameof(CanExecuteDecrement))]
21    public void Decrement()
22    {
23        this.Counter--;
24    }
25
26    public MyWindow()
27    {
28        IncrementCommand = DelegateCommandFactory.CreateDelegateCommand(Increment, () => CanExecuteIncrement);
29        DecrementCommand = DelegateCommandFactory.CreateDelegateCommand(Decrement, () => CanExecuteDecrement);
30    }
31
32    public DelegateCommand DecrementCommand { get; }
33
34    public DelegateCommand IncrementCommand { get; }
35}

Example: Commands with a CanExecute property and [Observable]

The following example demonstrates the code generated when the [Command] and [Observable] aspects are used together. Notice the compactness of the source code and the significant size of the generated code.

Source Code
1using System.Windows;
2using Metalama.Patterns.Observability;

3using Metalama.Patterns.Wpf;
4
5namespace Doc.Command.CanExecute_Observable;
6
7[Observable]
8public class MyWindow : Window
9{
10    public int Counter { get; private set; }
11


12    [Command]





13    public void Increment()












14    {
15        this.Counter++;
16    }
17
18    public bool CanExecuteIncrement => this.Counter < 10;
19
20    [Command]
21    public void Decrement()
22    {
23        this.Counter--;
24    }
25
26    public bool CanExecuteDecrement => this.Counter > 0;
27}
Transformed Code
1using System.ComponentModel;
2using System.Windows;
3using Metalama.Patterns.Observability;
4using Metalama.Patterns.Wpf;
5
6namespace Doc.Command.CanExecute_Observable;
7
8[Observable]
9public class MyWindow : Window, INotifyPropertyChanged
10{
11    private int _counter;
12
13    public int Counter
14    {
15        get
16        {
17            return _counter;
18        }
19
20        private set
21        {
22            if (_counter != value)
23            {
24                _counter = value;
25                OnPropertyChanged("CanExecuteDecrement");
26                OnPropertyChanged("CanExecuteIncrement");
27                OnPropertyChanged("Counter");
28            }
29        }
30    }
31
32    [Command]
33    public void Increment()
34    {
35        this.Counter++;
36    }
37
38    public bool CanExecuteIncrement => this.Counter < 10;
39
40    [Command]
41    public void Decrement()
42    {
43        this.Counter--;
44    }
45
46    public bool CanExecuteDecrement => this.Counter > 0;
47
48    public MyWindow()
49    {
50        IncrementCommand = DelegateCommandFactory.CreateDelegateCommand(Increment, () => CanExecuteIncrement, this, "CanExecuteIncrement");
51        DecrementCommand = DelegateCommandFactory.CreateDelegateCommand(Decrement, () => CanExecuteDecrement, this, "CanExecuteDecrement");
52    }
53
54    public DelegateCommand DecrementCommand { get; }
55
56    public DelegateCommand IncrementCommand { get; }
57
58    protected virtual void OnPropertyChanged(string propertyName)
59    {
60        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
61    }
62
63    public event PropertyChangedEventHandler? PropertyChanged;
64}

Async commands

When the Execute method returns a Task, the [Command] aspect implements an asynchronous command, which means that the ICommand.Execute method returns immediately (i.e., after the first non-synchronous await). The aspect generates a property of type AsyncDelegateCommand, which implements INotifyPropertyChanged and exposes the following members:

By default, the CanExecute property returns false if the previous call of the Execute method is still running. To allow for concurrent execution, set the CommandAttribute.SupportsConcurrentExecution property to true.

To track and cancel concurrent executions of the command, subscribe to the Executed event and use the DelegateCommandExecution object.

Background commands

By default, the implementation method of the command is executed in the foreground thread. You can dispatch its execution to a background thread by setting the CommandAttribute.Background property to true. This will work for implementation methods returning both void or a Task.

In both cases, the [Command] aspect generates a property of type AsyncDelegateCommand.

Customizing naming conventions

All examples above relied on the default naming convention, which is based on the following assumptions:

  • The command name is obtained by trimming the Execute method name (the one with the [Command] aspect) from:
    • prefixes: _, m_, and Execute,
    • suffix: _, Command, and Async.
  • Given a command name Foo determined by the previous step:
    • The command property is named FooCommand.
    • The CanExecute command or method can be named CanFoo, CanExecuteFoo, or IsFooEnabled.

This naming convention can be modified by calling the ConfigureCommand fabric extension method, then builder.AddNamingConvention, and supply an instance of the CommandNamingConvention class.

If specified, the CommandNamingConvention.CommandNamePattern is a regular expression that matches the command name from the name of the main method. If this property is unspecified, the default matching algorithm is used. The CanExecutePatterns property is a list of patterns used to select the CanExecute property or method, and the CommandPropertyName property is a pattern that generates the name of the generated command property. In the CanExecutePatterns and CommandPropertyName, the {CommandName} substring is replaced by the name of the command returned by CommandNamePattern.

Naming conventions are evaluated by priority order. The default priority is the order in which the convention has been added. It can be overwritten by supplying a value to the priority parameter.

The default naming convention is evaluated last and cannot be modified.

Example: Czech Naming Conventions

The following example illustrates a naming convention for the Czech language. There are two conventions. The first matches the Vykonat prefix in the main method, for instance, it will match a method named VykonatBlb and return Blb as the command name. The second naming convention matches everything and removes the conventional prefixes _ and Execute as described above. The default naming convention is never used in this example.

1using Metalama.Framework.Fabrics;
2using Metalama.Patterns.Wpf.Configuration;
3
4namespace Doc.Command.CanExecute_Czech;
5
6public class Fabric : ProjectFabric
7{
8    public override void AmendProject( IProjectAmender amender )
9    {
10        amender.ConfigureCommand(
11            builder =>
12            {
13                builder.AddNamingConvention(
14                    new CommandNamingConvention( "czech-1" )
15                    {
16                        CommandNamePattern = "^Vykonat(.*)$",
17                        CanExecutePatterns = ["MůžemeVykonat{CommandName}"],
18                        CommandPropertyName = "{CommandName}Příkaz"
19                    } );
20
21                builder.AddNamingConvention(
22                    new CommandNamingConvention( "czech-2" )
23                    {
24                        CanExecutePatterns =
25                            ["Můžeme{CommandName}"],
26                        CommandPropertyName = "{CommandName}Příkaz"
27                    } );
28            } );
29    }
30}
Source Code
1using System.Windows;
2using Metalama.Patterns.Wpf;
3
4namespace Doc.Command.CanExecute_Czech;
5
6public class MojeOkno : Window
7{
8    public int Počitadlo { get; private set; }
9
10    [Command]
11    public void VykonatZvýšení()
12    {
13        this.Počitadlo++;
14    }
15
16    public bool MůžemeVykonatZvýšení => this.Počitadlo < 10;
17
18    [Command]
19    public void Snížit()
20    {
21        this.Počitadlo--;
22    }
23
24    public bool MůžemeSnížit => this.Počitadlo > 0;
25}
Transformed Code
1using System.Windows;
2using Metalama.Patterns.Wpf;
3
4namespace Doc.Command.CanExecute_Czech;
5
6public class MojeOkno : Window
7{
8    public int Počitadlo { get; private set; }
9
10    [Command]
11    public void VykonatZvýšení()
12    {
13        this.Počitadlo++;
14    }
15
16    public bool MůžemeVykonatZvýšení => this.Počitadlo < 10;
17
18    [Command]
19    public void Snížit()
20    {
21        this.Počitadlo--;
22    }
23
24    public bool MůžemeSnížit => this.Počitadlo > 0;
25
26    public MojeOkno()
27    {
28        VykonatZvýšeníPříkaz = DelegateCommandFactory.CreateDelegateCommand(VykonatZvýšení, () => MůžemeVykonatZvýšení);
29        SnížitPříkaz = DelegateCommandFactory.CreateDelegateCommand(Snížit, () => MůžemeSnížit);
30    }
31
32    public DelegateCommand SnížitPříkaz { get; }
33
34    public DelegateCommand VykonatZvýšeníPříkaz { get; }
35}