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 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 return void and have zero or one parameter.
  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 System.Windows.Input;
3using Metalama.Patterns.Wpf;
4using Metalama.Patterns.Wpf.Implementation;
5
6namespace Doc.Command.SimpleCommand;
7
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    [Command]
19    public void Decrement()
20    {
21        this.Counter--;
22    }
23
24    public MyWindow()
25    {
26        IncrementCommand = new DelegateCommand(_ => Increment(), null);
27        DecrementCommand = new DelegateCommand(_ => Decrement(), null);
28    }
29
30    public ICommand DecrementCommand { get; }
31
32    public ICommand IncrementCommand { get; }
33}

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

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

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

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