Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / WPF

How to place all WPF commands and related events centrally in a static class

Rate me:
Please Sign up or sign in to vote.
4.78/5 (21 votes)
24 Jul 2008CPOL5 min read 92.6K   938   30   13
This class tell you how to place all WPF commands and their related CanExecute and Executed events centrally in a static class.

Preface

As this article is not intended to be a beginner's article, I presume you know what WPF commands are and what they are used for. But if you don't, I think this is the right time to learn about them. There are many good articles and books describing the nature and usage of WPF commands. One great article is by Richard Griffin, which you can read on his blog here: WPF Commands, a scenic tour: Part I. I also recommend reading Smart Routed Commands in WPF by Josh Smith for learning advanced concepts in WPF commands.

Introduction

Commands in WPF can be defined and maintained in a static class. The commands need to be bound with the window whose controls use them. To bind the commands, the CanExecute and Executed events need to defined in the window itself. But most of the time, people want all their commands and related CanExecute and Executed events in one single static class which makes it easier to maintain the program logic from one central place. The purpose of this article is to tell you how to enable this facility.

Defining the commands

Let's define some commands which we will be using for this example.

C#
public class MyAppCommands
{
    private static RoutedUICommand _AddContact;
    private static RoutedUICommand _EditContact;
    private static RoutedUICommand _DeleteContact;

    static MyAppCommands()
    {
        _AddContact = new RoutedUICommand("Add a new contact", 
                      "AddContact", typeof(MyAppCommands));
        _EditContact = new RoutedUICommand("Edit an existing contact", 
                       "EditContact", typeof(MyAppCommands));
        _DeleteContact = new RoutedUICommand("Delete an existing contact", 
                         "DeleteContact", typeof(MyAppCommands));
    }

    // Command: AddContact
    public static RoutedUICommand AddContact
    {
        get { return _AddContact; }
    }
    // Command: EditContact
    public static RoutedUICommand EditContact
    {
        get { return _EditContact; }
    }
    // Command: DeleteContact
    public static RoutedUICommand DeleteContact
    {
        get { return _DeleteContact; }
    }
}

Now, let us assume that we are using these three commands from a window which uses three buttons to fire the three commands, respectively.

XML
<Window x:Class="CentralCommands.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Commands="clr-namespace:CentralCommands"
    Title="MainWindow" Height="300" Width="300"
    x:Name="mainWindow">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Button HorizontalAlignment="Stretch" Margin="20" 
            Name="btnAdd" VerticalAlignment="Stretch" Grid.Row="0" 
            Command="Commands:MyAppCommands.AddContact">Add Contact</Button>
        <Button HorizontalAlignment="Stretch" Margin="20" 
            Name="btnEdit" VerticalAlignment="Stretch" Grid.Row="1" 
            Command="Commands:MyAppCommands.EditContact">Edit Contact</Button>
        <Button HorizontalAlignment="Stretch" Margin="20" 
            Name="btnDelete" VerticalAlignment="Stretch" Grid.Row="2" 
            Command="Commands:MyAppCommands.DeleteContact">Save Contact</Button>
    </Grid>
</Window>

As of now, if you execute this code, all the buttons will be disabled. The reason is that we have not bound the CanExecute and/or Executed events for the commands. If only CanExecute is defined, the button will only be enabled when the CanExecute event sets the CanExecute property of the CanExecuteRoutedEventArgs parameter passed to the method to true, but nothing will happen if you click the button. If Executed is defined for a command, the Executed method will be called when you click the button. If you only define Executed, the button using the command will be enabled, no matter what.

Let's define the CanExecute and Executed events for the commands in our MyAppCommands static class. The commands do nothing other than display a message box about what command was executed. AddContact and DeleteContact are enabled and EditContact is disabled for testing purposes.

C#
public static void AddContact_Executed(object sender, 
                   ExecutedRoutedEventArgs e)
{
    MessageBox.Show("Add contact command executed");
}
public static void AddContact_CanExecute(object sender, 
                   CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

public static void EditContact_Executed(object sender, 
                   ExecutedRoutedEventArgs e)
{
    MessageBox.Show("Edit contact command executed");
}
public static void EditContact_CanExecute(object sender, 
                   CanExecuteRoutedEventArgs e)
{
    e.CanExecute = false;
}

public static void DeleteContact_Executed(object sender, 
                   ExecutedRoutedEventArgs e)
{
    MessageBox.Show("Delete contact command executed");
}
public static void DeleteContact_CanExecute(object sender, 
                   CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

The problem

Now, we need to bind these events with the commands in the command bindings of the window. Let's try to define the events using the x:Static markup extension which binds any static by-value entity to the source (we only bind the AddContact command for the time being):

XML
<Window.CommandBindings>
    <CommandBinding Command="Commands:MyAppCommands.AddContact" 
            CanExecute="{x:Static Commands:MyAppCommands.AddContact_CanExecute}" 
            Executed="{x:Static Commands:MyAppCommands.AddContact_Executed}"/>
</Window.CommandBindings>

If you try to compile this code, you will get the following error:

CanExecute="{x:Static Commands:MyAppCommands.AddContact_CanExecute}" 
is not valid. '{x:Static Commands:MyAppCommands.AddContact_CanExecute}' 
is not a valid event handler method name. Only instance methods 
on the generated or code-behind class are valid.

Why are we getting the error? The event is defined as static in the MyAppCommand class, but we still cannot bind the event to the command. The reason is, the current WPF version's XAML does not allow us to bind event handlers in this way. The event handlers must be defined in the code-behind file inside the MainWindow class. I don't know if this is a bug, an accidently left out feature, or if we are not even supposed to use this functionality, but this stops us from defining a centralized location for handling all commands' Executed and CanExecute events.

The solution

The solution to this problem is to use code instead of XAML to define the bindings. Let us make a static function which will bind the commands and their respective events to the MainWindow window.

C#
public static void BindCommandsToWindow(Window window)
{
    window.CommandBindings.Add(
        new CommandBinding(AddContact, AddContact_Executed, AddContact_CanExecute));
    window.CommandBindings.Add(
        new CommandBinding(EditContact, EditContact_Executed, EditContact_CanExecute));
    window.CommandBindings.Add(
        new CommandBinding(DeleteContact, DeleteContact_Executed, DeleteContact_CanExecute));
}

We need to call this method from the MainWindow.Loaded event, passing the MainWindow instance:

C#
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    MyAppCommands.BindCommandsToWindow(this);
}

Now, try to run and execute the code.

Image 1

Voila! It works.

A small tip

You can send the command a parameter by using the CommandParameter dependency property of the button. As the data is sent as an object, you can send almost anything you want. The parameter is sent to both the CanExecute and Executed events. Let us suppose we want to set the status of Edit's CanExecute using code so that when Add is clicked, Edit is available, and when Delete is clicked, it's not. For clarity, I am making a separate class named CommandParameter which defines a bool property CanEditBeExecuted, although it can be done without defining a separate class at all. We also define a parameter property of a CommandParameter type and set CanEditBeExecuted to false in the constructor of MainWindow.

C#
public class CommandParameter
{
    public bool CanEditBeExecuted { get; set; }
}

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public CommandParameter parameter { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        Loaded += new RoutedEventHandler(MainWindow_Loaded);

        parameter = new CommandParameter();

        parameter.CanEditBeExecuted = false;
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        MyAppCommands.BindCommandsToWindow(this);
    }
}

Now, define the CommandParameter for the buttons:

XML
<Button HorizontalAlignment="Stretch" 
    Margin="20" Name="btnAdd" 
    VerticalAlignment="Stretch" Grid.Row="0" 
    Command="Commands:MyAppCommands.AddContact" 
    CommandParameter="{Binding ElementName=mainWindow, Path=parameter}">Add Contact
</Button>
<Button HorizontalAlignment="Stretch" Margin="20" 
    Name="btnEdit" VerticalAlignment="Stretch" Grid.Row="1" 
    Command="Commands:MyAppCommands.EditContact" 
    CommandParameter="{Binding ElementName=mainWindow, Path=parameter}">Edit Contact
</Button>
<Button HorizontalAlignment="Stretch" Margin="20" 
    Name="btnDelete" VerticalAlignment="Stretch" Grid.Row="2" 
    Command="Commands:MyAppCommands.DeleteContact" 
    CommandParameter="{Binding ElementName=mainWindow, Path=parameter}">Save Contact
</Button>

We also change the command's Execute and CanExecute handlers to reflect our strategy.

C#
public static void AddContact_Executed(object sender, ExecutedRoutedEventArgs e)
{
    CommandParameter parameter = e.Parameter as CommandParameter;

    if (parameter != null)
        parameter.CanEditBeExecuted = true;

    MessageBox.Show("Add contact command executed");
}
public static void AddContact_CanExecute(object sender, 
                   CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

public static void EditContact_Executed(object sender, 
                   ExecutedRoutedEventArgs e)
{
    MessageBox.Show("Edit contact command executed");
}
public static void EditContact_CanExecute(object sender, 
                   CanExecuteRoutedEventArgs e)
{
    CommandParameter parameter = e.Parameter as CommandParameter;

    if (parameter != null)
        e.CanExecute = parameter.CanEditBeExecuted;
    else
        e.CanExecute = false;
}

public static void DeleteContact_Executed(object sender, 
                   ExecutedRoutedEventArgs e)
{
    CommandParameter parameter = e.Parameter as CommandParameter;

    if (parameter != null)
        parameter.CanEditBeExecuted = false;

    MessageBox.Show("Delete contact command executed");
}
public static void DeleteContact_CanExecute(object sender, 
                   CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

If you will execute this code now, you will notice that the edit button does not reflect the changes made to the CanEditBeExecuted property. If you will search the net, the normal answer you will find is that this happens because the property cannot notify the button when it is changed. The solution is to derive CommandParameter from INotifyPropertyChanged and raise the property changed event when CanEditBeExecuted is changed. The answer is no. All you need to do is this: you need to instantiate the parameter object before InitializeComponent and the edit button will reflect the changes. So, if you change the constructor the following way, the edit button will reflect the changes made to CanEditBeExecuted.

C#
public MainWindow()
{
    parameter = new CommandParameter();
    InitializeComponent();
    Loaded += new RoutedEventHandler(MainWindow_Loaded);
    parameter.CanEditBeExecuted = false;
}

I just mentioned this tip because I think many people make this same mistake, and the solutions available on the net regarding the matter are mostly misleading. Although implementing INotifyPropertyChanged is a good idea, implementing it just because of the above mentioned reasons, is not.

Conclusion

I hope people who want a centralized static class to handle all the command functionality at one single place will find this article useful. Please feel free to comment or point out mistakes.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
India India

Comments and Discussions

 
QuestionCant recognize the The method members Executed and Can executed in XML . Pin
Member 1138291525-Jan-15 21:29
Member 1138291525-Jan-15 21:29 
SuggestionA little modification to make it ContextMenu-compliant Pin
GregJo9-Oct-12 11:02
GregJo9-Oct-12 11:02 
Questionvb Pin
CICCIOLO6916-Feb-12 3:52
CICCIOLO6916-Feb-12 3:52 
QuestionThank you Pin
Phan Dung12-Oct-11 1:32
Phan Dung12-Oct-11 1:32 
GeneralTQ Pin
Member 439878225-May-11 22:30
Member 439878225-May-11 22:30 
GeneralMy vote of 5 Pin
Member 439878225-May-11 22:28
Member 439878225-May-11 22:28 
GeneralThanks!!!! Pin
lemur19-Jan-10 1:57
lemur19-Jan-10 1:57 
GeneralPassing Data to the executed method Pin
Legz Jacobs16-Dec-09 21:23
Legz Jacobs16-Dec-09 21:23 
GeneralProblem when trying to set RibbonCommand to RibbonButton by code behind Pin
Jeanp866-Nov-09 21:03
Jeanp866-Nov-09 21:03 
GeneralRe: Problem when trying to set RibbonCommand to RibbonButton by code behind Pin
Yogesh Jagota7-Nov-09 5:19
Yogesh Jagota7-Nov-09 5:19 
GeneralElegant example Pin
McMsScan17-Sep-09 11:30
McMsScan17-Sep-09 11:30 
GeneralMy vote of 2 Pin
kunzhinz10-May-09 23:03
kunzhinz10-May-09 23:03 
GeneralThanks Pin
mheskol13-Aug-08 13:20
mheskol13-Aug-08 13:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.