Click here to Skip to main content
Click here to Skip to main content

WPF Routed Commands Simplified

, 22 Aug 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
In this article I will demonstrate a method to simplify the use of routed commands which is useful in non MVVM applications.

WPF brings many new concepts to the table and is far more powerful than WinForms. However as with many things in .NET, evolution sometimes brings along unwanted side effects. Routed commands in WPF bring a lot of features, unfortunately they also bring a lot of unnecessary complexity which causes many users to either create far more code than is necessary for a simple task, or avoid them completely.

The Problem

Create a WPF application with a main menu which supports keyboard shortcuts such as Ctrl-H.

This is a very basic need for most GUI applications. In WinForms this problem is so easily solved in a few seconds that one could not honorably define it as a problem. In WPF however, the solution is far from simple.

.NET is very powerful and in most areas very well designed. However from time to time areas of .NET suffer from abstraction without pragmatism. I have often described it as "It makes the hard stuff easy, but the easy stuff hard". Databinding in .NET 1.0 was the first notable example of this, and now we see it on a lesser degree with routed commands. Databinding was improved in later .NET versions, but routed commands in WPF are basically the same since introduction.

Demo - WinForms

First I will demonstrate a simple application using WinForms which will serve as our demo for comparison in WPF. The application is a single form application which has a main menu with two items. Hello and Bye. The two items are exclusive. You cannot select Bye before saying Hello, and vice versa except on startup where Hello can be used immediately. Selecting either of these items displays a message box.

Hello is available, but Bye is disabled.

Clicking Hello causes this dialog to appear.

 

After selecting Hello, Bye is now enabled but Hello is disabled.

The hot keys (Ctrl+H and Ctrl+B) for the menu items were specified by selecting the menu item in the form designer, and entering the shortcut into the property ShortcutKeys.

The code for this application is shown below.

  public partial class Form1 : Form {
    public Form1() {
      InitializeComponent();
    }

    private void helloToolStripMenuItem_Click(object sender, EventArgs e) {
      byeToolStripMenuItem.Enabled = true;
      helloToolStripMenuItem.Enabled = false;
      MessageBox.Show("Hello");
    }

    private void byeToolStripMenuItem_Click(object sender, EventArgs e) {
      byeToolStripMenuItem.Enabled = false;
      helloToolStripMenuItem.Enabled = true;
      MessageBox.Show("Bye");
    }
  }

MVVM

The previous WinForms demo makes no attempt at being an MVVM or even an MVP or MVC application. One of the design ideas behind routed commands is MVVM. The problem is that they were done in a way which makes their implementation clumsy at best for the developer requiring code scattered in several places and because of how they are used, this side effect often causes more problems than it is intended to solve. Please note that I am not speaking of MVVM itself, only that of the consequences of the implementation of routed commands.

In the initial version of this article many of the points were not made well. Because of this I have integrated stock based examples from MSDN and other sources to better illustrate the points of the article.

In this article I take no position on MVVM. But I to take issue that MVVM is not suited for all scenarios and that the overhead and being forced into such a decision as a "one size fits all" approach is inappropriate. In fact I quote from Wikipedia:

"A criticism of the pattern comes from MVVM creator John Gossman himself, who points out that the overhead in implementing MVVM is "overkill" for simple UI operations. He also states that for larger applications, generalizing the View layer becomes more difficult. Moreover, he illustrates that data binding in very large applications can result in considerable memory consumption."

This pretty much sums up the issue with routed commands in non MVVM applications. The problem is not MVVM, but being coerced into such a situation for all cases. John mentions that it becomes difficult in large applications. This is true, but in fact it is the large applications that benefit the most from MVVM. In smaller applications it usually just "gets in the way". And yes, I realize that sometimes small applications become big ones but most don't and those that do can easily be reengineered before they become too large.

Quite simply - the need to add Ctrl-H handling into an application should not require significant code or a change to MVVM simply for this feature.

WPF

WPF is the successor to WinForms. WinForms is in maintenance mode. WPF is mature.

Given these factors, porting the previous demo to WPF should be straightforward. Unfortunately no so much. Well actually it is, unless you want hot key support (Ctrl-B, Ctrl-H). That little detail creates a complication in WPF applications.

To support hot keys, one can no longer use the main menu component solely but is required to create routed commands and bindings or provide key support via keypress events.

Demo - WPF (Standard)

Maybe "standard" isn't the best name for the demo but we have to start somewhere. Becuase both XAML and C# are extremely versatile, there are numerous ways to implement routed commands. In this demo I have used one of the commonly accepted methods of using routed commands. In fact I have taken the demo from Understanding Routed commands. (Note, I found no license mentioned, but I am attributing by linking to the original article)

To this demo I have made only minimal changes. Because this article concerns use of the menu, I added a menu and also introduced two comamnds instead of one. I also chagned the logic to perform Hello / Bye as shown in the WinForms application. Otherwise I have left the demo alone and the routed command and all other structures have been preserved exactly.

Window

<Window x:Class="RoutedCommandDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:RoutedCommandDemo"
    Title="Window1" Height="300" Width="300">
  <Window.InputBindings>
    <KeyBinding Command="{x:Static local:Commands.Hello}" Modifiers="Control" Key="H"/>
    <KeyBinding Command="{x:Static local:Commands.Bye}" Modifiers="Control" Key="B"/>
  </Window.InputBindings>
  <Window.CommandBindings>
    <CommandBinding
      Command="{x:Static local:Commands.Hello}"
      CanExecute="Hello_CanExecute"
      Executed="Hello_Executed"
      />
    <CommandBinding
      Command="{x:Static local:Commands.Bye}"
      CanExecute="Bye_CanExecute"
      Executed="Bye_Executed"
      />
  </Window.CommandBindings>

  <Grid>
    <Menu HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Name="menuMain">
      <MenuItem Header="_File">
        <MenuItem Header="_Hello" Name="mitmFile_Hello" InputGestureText="Ctrl+H" Command="{x:Static local:Commands.Hello}"/>
        <MenuItem Header="_Bye" Name="mitmFile_Bye" InputGestureText="Ctrl+B" Command="{x:Static local:Commands.Bye}"/>
      </MenuItem>
    </Menu>
    <StackPanel Margin="0,48,0,0">
    <Button 
      Command="{x:Static local:Commands.Hello}" 
      Content="Hello"
      />
    <Button 
      Command="{x:Static local:Commands.Bye}" 
      Content="Bye"
      />
    </StackPanel>
  </Grid>
</Window>

Commands.cs

  public static class Commands {
    public static readonly RoutedCommand Hello = new RoutedCommand();
    public static readonly RoutedCommand Bye = new RoutedCommand();
  }

Code behind window

  public partial class Window1 : Window {
    public Window1() {
      InitializeComponent();
    }

    bool mHelloSaid = false;

    void Hello_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
      e.CanExecute = !mHelloSaid;
    }

    void Hello_Executed(object sender, ExecutedRoutedEventArgs e) {
      mHelloSaid = true;
      MessageBox.Show("Hello");
    }

    void Bye_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
      e.CanExecute = mHelloSaid;
    }

    void Bye_Executed(object sender, ExecutedRoutedEventArgs e) {
      mHelloSaid = false;
      MessageBox.Show("Bye!");
    }
  }

The goal of this article is not to explain routed commands in detail, but instead show a technique to simplify their use. If you want to read more about routed commands, here are some articles:

The first thing most developers will notice is that the size and complexity of the code versus the WinForms demo has increased significantly. I have followed the pattern that is most prevalent among demos from the links above as well as MSDN. They do vary on aspects of XAML and some move some code to C#, but I have strictly adhered to the original demo in its architecture.

To better illustrate the issues let us see what is required to add a new user option. First, we must add a new menu item, a new button, and code that will execute. This is a given and necessary in any application. In WinForms we would be finished. But with routed commands we are not there yet.

Step 1 - Command

  public static class Commands {
    public static readonly RoutedCommand Hello = new RoutedCommand();
    public static readonly RoutedCommand Bye = new RoutedCommand();
    public static readonly RoutedCommand Exit = new RoutedCommand();
  }

Step 2 - InputBinding

  <Window.InputBindings>
    <KeyBinding Command="{x:Static local:Commands.Hello}" Modifiers="Control" Key="H"/>
    <KeyBinding Command="{x:Static local:Commands.Bye}" Modifiers="Control" Key="B"/>
    <KeyBinding Command="{x:Static local:Commands.Exit}" Modifiers="Control" Key="E"/>
  </Window.InputBindings>

Ctrl+E is generally not a good idea for Exit, it is used only for illustrative purposes.

Step 3 - CommandBinding

  <Window.CommandBindings>
    <CommandBinding
      Command="{x:Static local:Commands.Hello}"
      CanExecute="Hello_CanExecute"
      Executed="Hello_Executed"
      />
    <CommandBinding
      Command="{x:Static local:Commands.Bye}"
      CanExecute="Bye_CanExecute"
      Executed="Bye_Executed"
      />
    <CommandBinding
      Command="{x:Static local:Commands.Exit}"
      CanExecute="Exit_CanExecute"
      Executed="Exit_Executed"
      />
  </Window.CommandBindings>

Just to add a single new action, we had to edit three additional sections of code in separate areas. As the options increase, your Window code often gets swamped by these simple structures. 

 

This code is also quite brittle and can be prone to mistakes. Intellisense is not available in many cases:

img height="82px" src="SNAG-0010.png" width="347px" />

Visual Studio usually offers help with descriptive error messages, but in XAML binding there are many cases as well where Visual Studio will not catch the errors until run time. In the binding method that is used here, it is generally reliable however requires frequent recompiles as it does not parse the code as would happen while working in C#, but instead relies on the last compiled output.

Refactoring or renaming commands will not propagate into the XAML and also must be done by hand.

Solution

The idea of separating commands from the UI is a great one. It allows easier code consolidation for example when you have a menu item and a tool bar button which execute the same action. It is the implementation that feels immature though and showing no signs of improvement for non MVVM applications.

Delphi tried something similar with action lists. Delphi got the RAD part right, but unfortunately Delphi's actions were too limited and suffered from other design issues. Ultimately it would be nice to have a visual editor for commands, but extending Visual Studio is far more complex than it should be. And before anyone jumps on me saying "oh its easy, you just don't know how", consider two things. First, while Delphi is more limited in its extensibility, one can easily write a IDE plugin within a few minutes. Something that is not generally possible in Visual Studio. Secondly, I am one of the founders of the Cosmos project which is one of the largest Visual Studio extensions available. Back to our discussion of routed commands....

Because of the above factors I wanted to create something in the middle. I didn't have time to create a full Visual Studio component set or editor, but I certainly could improve the situation with some minimal effort. This solution also requires the use of a menu, which your application may not use. The idea however can be expanded and one could even create an invisible menu bar to use. The idea behind the menu is that its a "hacked" visual editor for the routed commands.

Demo - Routed2

Window

<Window x:Class="Routed2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
  <Grid>
    <Menu HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Name="menuMain">
      <MenuItem Header="_File">
        <MenuItem Header="_Hello" Name="mitmFile_Hello" InputGestureText="Ctrl+H" Click="mitmFile_Hello_Click" />
        <MenuItem Header="_Bye" Name="mitmFile_Bye" IsEnabled="False" InputGestureText="Ctrl+B" Click="mitmFile_Bye_Click" />
      </MenuItem>
    </Menu>
    <StackPanel Margin="0,48,0,0">
      <Button Name="butnHello" Content="Hello" />
      <Button Name="butnBye" Content="Bye"/>
    </StackPanel>
  </Grid>
</Window>

Commands.cs (No longer needed)


 

Code behind window

  public partial class MainWindow : Window {

    public MainWindow() {
      InitializeComponent();

      SimpleCommand.ScanMenu(this, menuMain.Items);
      butnHello.Command = mitmFile_Hello.Command;
      butnBye.Command = mitmFile_Bye.Command;
    }

    private void mitmFile_Hello_Click(object sender, RoutedEventArgs e) {
      mitmFile_Hello.IsEnabled = false;
      mitmFile_Bye.IsEnabled = true;
      MessageBox.Show("Hello");
    }

    private void mitmFile_Bye_Click(object sender, RoutedEventArgs e) {
      mitmFile_Bye.IsEnabled = false;
      mitmFile_Hello.IsEnabled = true;
      MessageBox.Show("Bye");
    }

  }

It should be apparent that this code is far simpler. One simply needs to design the menu and define click events. CanExecute is hooked by default to an event which checks to see if the menu item is enabeld or not, and the hotkey is taken from InputGestureText. The "magic" occurs in the call in the ctor to .ScanMenu. The source for this is available in SimpleCommand.cs and is only a few dozen lines of code.

To add a new item such as Exit, one only needs to add a new menu item. The rest is assembled for you, and the command and bindings are still accessible should you wish to further modify their behavior.

This is not a catch all solution and of course has limitations. It immensely helps me in one type of LOB apps that I develop frequently. Developers will continue to point out that its not perfect, and that is OK.

Speling!

The CodeProject editor has no built in spell checker, and it does not work with the build in browser spellchecker. I have tried to catch spelling errors, but if you happen to find any please report them. I have also lived my life between countries which use at least 4 different versions of English and sometimes you may find a British spelling, other times an American one. I have done my best to standardize them to American English.

License

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

Share

About the Author

Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
www.KudzuWorld.com
 
Formerly the Regional Developer Adviser (DPE) for Microsoft Middle East and Africa, he was responsible for 85 countries spanning 4 continents and 10 time zones. Now Chad is a Microsoft MVP.
 
Chad is the chair of several popular open source projects including Indy and Cosmos (C# Open Source Managed Operating System).
 
Chad is the author of the book Indy in Depth and has contributed to several other books on network communications and general programming.
 
Chad has lived in Canada, Cyprus, Switzerland, France, Jordan, Russia, Turkey, and the United States. Chad has visited more than 60 countries, visiting most of them several times.

Comments and Discussions

 
GeneralMy vote of 2 PinmemberMahBulgaria22-Aug-14 4:11 
GeneralWell done Chad PinmentorEspen Harlinn22-Aug-14 3:05 
GeneralRe: Well done Chad PinmemberChad Z. Hower aka Kudzu22-Aug-14 3:10 
GeneralRe: Well done Chad PinmentorEspen Harlinn22-Aug-14 3:14 
Questionsorry PinmvpSacha Barber21-Aug-14 20:13 
AnswerRe: sorry PinmemberChad Z. Hower aka Kudzu22-Aug-14 7:39 
GeneralMy vote of 2 PinprofessionalIan Shlasko21-Aug-14 9:33 
GeneralRe: My vote of 2 PinmemberChad Z. Hower aka Kudzu21-Aug-14 12:08 
GeneralRe: My vote of 2 PinprofessionalIan Shlasko21-Aug-14 12:29 
GeneralRe: My vote of 2 PinmvpSacha Barber21-Aug-14 20:10 
GeneralRe: My vote of 2 PinmemberChad Z. Hower aka Kudzu22-Aug-14 3:31 
GeneralRe: My vote of 2 PinmvpSacha Barber22-Aug-14 4:21 
GeneralRe: My vote of 2 PinmemberChad Z. Hower aka Kudzu22-Aug-14 4:42 
GeneralRe: My vote of 2 PinmemberSledgeHammer0121-Aug-14 15:34 
GeneralRe: My vote of 2 PinmemberChad Z. Hower aka Kudzu21-Aug-14 17:06 
GeneralRe: My vote of 2 PinmemberSledgeHammer0121-Aug-14 18:08 
GeneralRe: My vote of 2 PinmemberChad Z. Hower aka Kudzu21-Aug-14 19:28 
Question[My vote of 1] I agree with Sacha... PinmemberSledgeHammer0121-Aug-14 8:10 
AnswerRe: [My vote of 1] I agree with Sacha... PinmemberChad Z. Hower aka Kudzu21-Aug-14 8:25 
AnswerRe: [My vote of 1] I agree with Sacha... PinmemberChad Z. Hower aka Kudzu21-Aug-14 8:29 
GeneralRe: [My vote of 1] I agree with Sacha... PinmemberSledgeHammer0121-Aug-14 8:49 
AnswerRe: [My vote of 1] I agree with Sacha... PinmemberChad Z. Hower aka Kudzu21-Aug-14 8:40 
GeneralRe: [My vote of 1] I agree with Sacha... PinmemberSledgeHammer0121-Aug-14 8:57 
AnswerRe: [My vote of 1] I agree with Sacha... PinmemberKlaus Luedenscheidt21-Aug-14 18:32 
GeneralRe: [My vote of 1] I agree with Sacha... PinmemberChad Z. Hower aka Kudzu21-Aug-14 19:05 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 22 Aug 2014
Article Copyright 2014 by Chad Z. Hower aka Kudzu
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid