Open sandboxFocusImprove this doc

WPF commands

In WPF, a command is an object that implements the ICommand interface. You can bind commands to UI controls like buttons to trigger actions and 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 requires substantial boilerplate code, especially to support the CanExecuteChanged event.

The [Command] aspect automatically generates WPF command boilerplate. When you apply it 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 the Metalama.Patterns.Wpf package to your project.

  2. Add the [Command] attribute to the method that executes when the command is invoked. This method becomes 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. 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 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.

Associate a CanExecute implementation with the Execute member in one of two ways:

  • Implicitly, by following naming conventions. For a command named Foo, the CanExecute member can be named CanFoo, CanExecuteFoo, or IsFooEnabled. To customize these naming conventions, see the section below.
  • 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 is raised whenever the CanExecute property changes. Use the [Observable] aspect to implement INotifyPropertyChanged. For details, see Metalama.Patterns.Observability.

Example: commands with a CanExecute property and implicit association

The following example demonstrates two commands, Increment and Decrement, coupled to properties that determine whether 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 preceding one, but uses the CanExecuteProperty property to explicitly associate the CanExecute property with the 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 you use the [Command] and [Observable] aspects together. Notice the compact source code compared to the substantial 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, meaning the ICommand.Execute method returns immediately (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 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 command implementation method executes in the foreground thread. Dispatch execution to a background thread by setting the CommandAttribute.Background property to true. This works for implementation methods returning both void and Task.

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

Customizing naming conventions

The preceding examples rely 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
    • Suffixes: _, 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.

Modify this naming convention by calling the ConfigureCommand fabric extension method, then builder.AddNamingConvention, and supplying 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 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 CanExecutePatterns and CommandPropertyName, the {CommandName} substring is replaced by the command name returned by CommandNamePattern.

Naming conventions are evaluated by priority order. The default priority is the order in which you added the convention. Override it by supplying a value to the priority parameter.

The default naming convention is evaluated last and can't 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 matches a method named VykonatBlb and returns Blb as the command name. The second naming convention matches everything and removes the conventional prefixes _ and Execute as described previously. The default naming convention isn't 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( builder =>
11        {
12            builder.AddNamingConvention(
13                new CommandNamingConvention( "czech-1" )
14                {
15                    CommandNamePattern = "^Vykonat(.*)$",
16                    CanExecutePatterns = ["MůžemeVykonat{CommandName}"],
17                    CommandPropertyName = "{CommandName}Příkaz"
18                } );
19
20            builder.AddNamingConvention(
21                new CommandNamingConvention( "czech-2" )
22                {
23                    CanExecutePatterns =
24                        ["Můžeme{CommandName}"],
25                    CommandPropertyName = "{CommandName}Příkaz"
26                } );
27        } );
28    }
29}
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}