Click here to Skip to main content
15,886,676 members
Articles / Desktop Programming / WPF

Extending WPF Commands

Rate me:
Please Sign up or sign in to vote.
4.65/5 (16 votes)
26 Jul 2008CPOL9 min read 69.6K   1.1K   41   14
How to overcome the limitations of the WPF Command system.

Introduction

WPF Commands enable you to have loosely coupled UI elements that nonetheless have the ability to act together nicely. Have a look at this standalone XAML code snippet:

XML
<Window x:Class="ExtendedCommands.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="150" Width="150">
    <Grid>
        <DockPanel>
            <Menu DockPanel.Dock="Top">
                <MenuItem Command="{x:Static EditingCommands.ToggleBold}" 
                          Header="Bold"/>
            </Menu>
            <RichTextBox x:Name="richTextBox"/>
        </DockPanel>
    </Grid>
</Window>

sample0.gif

In this example, the Bold menu item is disabled if the text box has the input focus, but is enabled when the RichTextBox has input focus. Furthermore, when clicking the menu item, the bold flag of the current text selection in the RichTextBox is automatically toggled when clicking the button. No custom code required!

You might have seen this magic in a WPF demonstration before. The reason this works is that the RichTextBox has default command implementations for most EditingCommands such as ToggleBold or ToggleItalic.

This behavior is impressive, but in a real world application, the situation is a bit more complicated. In case of a text editor, a bold button for example would not only be enabled/disabled depending on whether or not an appropriate input element has the focus, it would also reflect the state of the currently selected text. If the user selects a bold text, the button would be checked, and if the user selects normal text, the button would be unchecked. WPF Commands do not support such a scenario out of the box. This article discusses different ways of solving this problem and provides an implementation of the, in my opinion, most powerful solution.

This article does not give an introduction to WPF Commands, and assumes that the reader already knows how Commands work and how to implement custom CommandBindings. For an introduction to WPF Commands, see this article: WPF: A Beginner's Guide - Part 3 of n by Sacha Barber[^].

Why it does not work

CanExecuteRoutedEventArgs.gif

When implementing a CommandBinding, you have the choice to implement two methods. A method which is called when the command is executed, and a method which is called when the command queries whether or not the command can be executed. In the execute method, you have access to an argument of type CanExecuteRoutedEventArgs which gives you (among other things) access to the Command, to the Parameter, and most importantly, allows you to set the CanExecute value. Controls which support Commands (by implementing ICommandSource) such as MenuItem or controls which derive from ButtonBase (Button, ToggleButton) can enable/disable themselves according to the CanExecute value of the CanExecuteRoutedEventArgs.

Because this is all the information they can access, their behavior is limited to this, and this is why the ToggleButton or the MenuItem which both have an IsChecked property cannot check themselves when using Commands. They simply don’t have access to the necessary information!

Options

I am briefly discussing a few different solutions here. Some parts of this section require an advanced understanding of the WPF Command system so you might want to skip this. As far as I am aware, there are four possible solutions:

1. Use normal Commands and write some glue code to overcome this shortcoming

The quickest and most dirty solution to this issue is to glue the CommandSource (MenuItem) and the CommandTarget (<code>RichTextBox) together and manually set the IsChecked property of the CommandSource by observing the CommandTarget. Albeit doing this is easy, it is potentially work intensive since you have to do this for every object, and error prone, but most importantly, it completely defeats the purpose of Commands and does not allow you to have a loosely coupled UI.

2. Use a custom ICommand implementation which can hold a CurrentValue

Write your own flavor of the RoutedCommand class and provide a settable CurrentValue property which could be set in the CanExecute method and be observed by CommandTargets that use the Command. I don’t want to explain the drawbacks in too much detail, so here is the bottom line of this approach:

  1. Commands are static objects and therefore you could only have one CurrentValue per Command. While this might not be a problem in simple scenarios, once you have multiple top level windows or the same Command on different CommandSources target different CommandTargets, it doesn’t work anymore.
  2. If you go down this route, you cannot use any of the already provided Commands and you have to invent a new Command as soon as you need to know more than just a CanExecute flag.

3. Do not use Commands

WPF Commands do not allow you to do this out of the box, therefore you might want to write your own Command solution and not use the in-built Commands at all. While I am not sure how much work would be involved to do this, I think this solution would be the cleanest solution. Nevertheless I have decided to go with a different solution. Solution number 4.

The main reasons for doing this is that solution number 4 is relatively straightforward to implement, has only have a couple of insignificant disadvantages (which I am discussing later), and will hopefully not require a lot of changes when Microsoft will address this shortcoming in future versions (if that ever happens).

4. Use a custom class as a parameter to transport more information

In this solution, we leverage the already existing command system, but instead of passing the normal Parameter to the CanExecute handler, we pass the Parameter wrapped into our own class which allows setting a CurrentValue. The cleaner way of doing this would have been to create our own class which inherits from the CanExecuteRoutedEventArgs class, but unfortunately, this class is sealed, so we don’t have this option. The rest of this article shows you how to implement this solution and discusses the implementation details.

A solution

Currently, Commands are executed in two steps:

  1. See if the Command can execute on the target
  2. Execute the Command

The first step is done repeatedly, for example, when the focused element changes or the user clicks with the mouse or types on the keyboard, in order to ensure that the CommandSources are enabled/disabled accordingly. To allow more flexibility and enable the ToggleBold scenario I have described in the introduction, we need to change the first step. Instead of just checking whether or not the Command can execute, we want to enable access to more information.

  1. See if the Command can execute on the target and more!
  2. Execute the Command.

In order to do this, we need to tweak the components who are involved a bit. In the end, we will have a MenuItem that checks itself according to a current value which will be delivered by the implementation of the CommandBinding.

How it works

The solution uses a class called CommandCanExecuteParameter which serves as the medium to report information from the CommandTarget (RichTextBox to the CommandSource (MenuItem)).

C#
public class CommandCanExecuteParameter
{
    public CommandCanExecuteParameter(object parameter)
    {
        Parameter = parameter;
    }

    public object Parameter { get; private set; }
    public object CurrentValue { get; set; }
}

In the CommandBinding implementation, we simply set the CurrentValue if the Parameter of the CanExecuteRoutedEventArgs is of type CommandCanExecuteParameter.

C#
private void command_ToggleBold_canExecute (object sender, CanExecuteRoutedEventArgs e)
{
    if (e.Parameter is CommandCanExecuteParameter)
    {
        (e.Parameter as CommandCanExecuteParameter).CurrentValue = GetBold(richTextBox);
    }

    e.CanExecute = true;
    e.Handled = true;
}

Next, we implement a XMenuItem class which inherits from a MenuItem and provides its own implementation of a UpdateCanExecute method (to make use of our CommandCanExecuteParameter). Internally, the MenuItem uses a UpdateCanExecute method to achieve the default behavior but unfortunately, the UpdateCanExecute method of the MenuItem is private and we cannot modify the behavior. Instead, we wire up our own code and set up our own UpdateCanExecute method whenever the current Command changes. The important thing to know here is that the system internally stores the handlers of the UpdateCanExecute event in a WeakReference list. This means that we have to hold a reference to our own handler in order to prevent the handler from being garbage collected.

C#
static XMenuItem()
{
    CommandProperty.OverrideMetadata(typeof(XMenuItem), 
       new FrameworkPropertyMetadata(OnCommandChanged));
}

private static void OnCommandChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
{
    ((XMenuItem)d).OnCommandChanged(e.OldValue as ICommand, e.NewValue as ICommand);
}

private void OnCommandChanged(ICommand oldValue, ICommand newValue)
{
    if (oldValue != null)
        oldValue.CanExecuteChanged -= OnCanExecuteChanged;

    if (newValue != null)
    {
        //the command system uses WeakReferences internally,
        //so we have to hold a reference to the canExecuteChanged handler ourselves

        if (canExecuteChangedHandler == null)
            canExecuteChangedHandler = OnCanExecuteChanged;
        
        newValue.CanExecuteChanged += canExecuteChangedHandler;
    }
    else
        canExecuteChangedHandler = null;
}
//hold a reference to the canExecuteChangedHandler
//so that it is not garbage collected
private EventHandler canExecuteChangedHandler;

private void OnCanExecuteChanged(object sender, EventArgs e)
{
    UpdateCanExecute();
}

In the UpdateCanExecute method, we create an instance of our CommandCanExecuteParameter class and call the CanExecute handler of the command. We then set the IsChecked property according to the reported CurrentValue.

Please note that we do not have to set the IsEnabled property because the default behavior of the parent class (MenuItem) is still executing. We only have to do the additional work.

C#
private void UpdateCanExecute()
{
    if (IsCommandExecuting)
        return;

    IsCommandExecuting = true;
    try
    {
        //use our custom class as the parameter
        var parameter = new CommandCanExecuteParameter(null);
        CommandUtil.CanExecute(this, parameter);
        //we set the current status independent on whether the command can execute
        {
            if (parameter.CurrentValue is bool)
            {
                IsChecked = (bool)parameter.CurrentValue;
            }
            else
            {
                IsChecked = false;
            }
        }
    }
    finally
    {
        IsCommandExecuting = false;
    }
}

Then we add the XMenuItem in the XAML file and voila, the menu item now sets the IsChecked property automatically!

sample1_2.gifsample1_2.gif

XML
<Window x:Class="ExtendedCommands.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Controls="clr-namespace:Wpf.Controls;assembly=Wpf"
    Title="Window1" 
    Height="150" 
    Width="150">
    <Grid>
        <DockPanel>
            <Menu DockPanel.Dock="Top">
                <Controls:XMenuItem 
                    Command="{x:Static EditingCommands.ToggleBold}" 
                    Header="Bold (XMenuItem)"/>
                <Separator/>
                <MenuItem 
                    Command="{x:Static EditingCommands.ToggleBold}" 
                    Header="Bold (MenuItem)"/>
            </Menu>
            <TextBox DockPanel.Dock="Top"/>
            <RichTextBox x:Name="richTextBox"/>
        </DockPanel>
    </Grid>
</Window>

What you should be aware of

Because this solution sits on top of the normal WPF Command system, the UpdateCanExecute handler is called twice. Once from the normal implementation, and once from the custom implementation. While this seems to be an overhead, it is actually a good thing because the solution will not interfere with already existing CommandBindings and simply adds value if the CommandBinding knows how to deal with the CommandCanExecuteParameter. To keep this behaviour consistent in your own ICommandSource aware classes, you should call the CanExecute handler twice. Once with the normal Parameter and once with the CommandCanExecuteParameter.

Providing a more complete solution

So far we only tweaked a MenuItem and the first sample file contains the solution I have described above.

In a real world application, there are, of course, other important controls that should make use of this extended Command system. The second sample includes the following controls, some of which do not natively support Commands at all!

  • XToggleButton
  • XCheckBox
  • XSlider
  • XComboBox

XToggleButton and XCheckBox

XToggleButton and XCheckBox have virtually the same implementation as the XMenuItem and support a bool as the CurrentValue.

XSlider

The default Slider does not support Commands out of the box, so the XSlider implements the ICommandSource and supports float, double, or a more sophisticated RangedValue as the CurrentValue. The former allows to set the Minimum and Maximum properties from within the CanExecute handler. The XSlider also adds a Precision property which makes it easier to get values like 1.23 instead of 1.235837128.

The XSlider also contains a hack (thanks to Dr. WPF) to make the behavior more consistent with the Office sliders. See this forum post for details: http://forums.msdn.microsoft.com/en-US/wpf/thread/5fa7cbc2-c99f-4b71-b46c-f156bdf0a75a.

XComboBox

The default ComboBox lacks support for Commands just like the default Slider. The XComboBox supports any object contained in the XComboBox as the CurrentValue and also supports the string representation of an object as the CurrentValue.

The second sample

In the second sample, I have put all these controls on the main window and implemented some simple CommandBindings. As you can see, I spent hours on the look and feel:

sample2.gif

XML
<Window x:Class="ExtendedCommands.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Controls="clr-namespace:Wpf.Controls;assembly=Wpf"
    xmlns:Wpf="clr-namespace:Wpf;assembly=Wpf"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <DockPanel>
            <Menu DockPanel.Dock="Top">
                <Controls:XMenuItem 
                    Command="{x:Static EditingCommands.ToggleBold}" 
                    Header="Bold"/>
            </Menu>
            <StackPanel 
                DockPanel.Dock="Top" 
                Orientation="Horizontal" 
                FocusManager.IsFocusScope="True">
                <Controls:XCheckBox 
                    Command="{x:Static EditingCommands.ToggleBold}">Bold
                </Controls:XCheckBox>
                <Controls:XToggleButton 
                    Command="{x:Static EditingCommands.ToggleItalic}">Italic
                </Controls:XToggleButton>
                <TextBlock>Size:</TextBlock>
                <Controls:XSlider 
                    Precision="0" 
                    Command="{x:Static Wpf:MyCommands.SetFontSize}" 
                    Width="100"/>
                <TextBlock>Color:</TextBlock>
                <Controls:XComboBox x:Name="combo"
                    Command="{x:Static Wpf:MyCommands.SetFontColor}"/>
            </StackPanel>
            <RichTextBox x:Name="richTextBox"/>
        </DockPanel>
    </Grid>
</Window>

Credits

Thanks to my employer NovaMind (http:/www.novamind.com[^]) for allowing me to share this with the world. Thanks to my former team mate Super Lloyd[^] who was the first to implement this solution after we came up with the idea together.

Thanks

Thanks to all the nice people here on The Code Project who helped me to become a better programmer. I have been a member of the CodeProject for more than five years and this is my first contribution. Special thanks to the WPF Disciples, especially to Josh Smith, Dr. WPF, Karl Shifflett, and Sacha Barber for their numerous contributions on The Code Project, to Adam Nathan for his excellent book (WPF Unleashed), to the active members of the MSDN WPF Forum, to Charles Petzold, and to our local (Australian) WPF MVPs: Joseph Cooney (www.learnwpf.com[^]) and Paul Stovell (www.paulstovell.com[^]).

License

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


Written By
Software Developer
Australia Australia

Patrick is one of the guys behind the Windows version of the mind mapping software NovaMind (www.novamind.com[^]) and is very comfortable with C#, GDI+ and Windows Presentation Foundation.


Comments and Discussions

 
General2 year later still one question :P Pin
Bastien Neyer15-Aug-10 9:44
Bastien Neyer15-Aug-10 9:44 
GeneralRe: 2 year later still one question :P Pin
Patrick Klug1-Sep-10 15:22
Patrick Klug1-Sep-10 15:22 
GeneralError 3 Metadata file 'CSharp3rdPCodes=-\WPF\WpfCmdPattern\Commands\bin\Debug\Itenso.Solutions.Community.Commands.dll' could not be found C:\-=CSharp3rdPCodes=-\WPF\WpfCmdPattern\CommandDemo\CSC Pin
Sazuke-kun19-May-09 13:55
Sazuke-kun19-May-09 13:55 
GeneralRe: Error 3 Metadata file 'CSharp3rdPCodes=-\WPF\WpfCmdPattern\Commands\bin\Debug\Itenso.Solutions.Community.Commands.dll' could not be found C:\-=CSharp3rdPCodes=-\WPF\WpfCmdPattern\CommandDemo\CSC Pin
Sazuke-kun19-May-09 13:57
Sazuke-kun19-May-09 13:57 
GeneralRe: Error 3 Metadata file 'CSharp3rdPCodes=-\WPF\WpfCmdPattern\Commands\bin\Debug\Itenso.Solutions.Community.Commands.dll' could not be found C:\-=CSharp3rdPCodes=-\WPF\WpfCmdPattern\CommandDemo\CSC Pin
Sazuke-kun19-May-09 14:08
Sazuke-kun19-May-09 14:08 
GeneralTHIS CODE DOES NOT WORK WHEN PASTING TEXT,IF I PASTE TEXT IN IT ALWAYS BOLD Pin
steve ass9-Nov-08 13:45
steve ass9-Nov-08 13:45 
GeneralRe: THIS CODE DOES NOT WORK WHEN PASTING TEXT,IF I PASTE TEXT IN IT ALWAYS BOLD Pin
Patrick Klug9-Nov-08 19:19
Patrick Klug9-Nov-08 19:19 
GeneralRe: THIS CODE DOES NOT WORK WHEN PASTING TEXT,IF I PASTE TEXT IN IT ALWAYS BOLD Pin
Chris Maunder2-Mar-09 8:38
cofounderChris Maunder2-Mar-09 8:38 
GeneralGodspeed Pin
Kobi Udi4-Aug-08 22:49
Kobi Udi4-Aug-08 22:49 
GeneralRe: Godspeed Pin
Patrick Klug5-Aug-08 14:55
Patrick Klug5-Aug-08 14:55 
GeneralRe: Godspeed Pin
Kobi Udi9-Aug-08 20:07
Kobi Udi9-Aug-08 20:07 
GeneralAwesome job! Pin
Josh Smith27-Jul-08 7:02
Josh Smith27-Jul-08 7:02 
This is great work. Also a fine first article, I might add! I'm looking forward to reading more articles of yours... Wink | ;)

Got my 5.

:josh:
My WPF Blog[^]
Sleep is overrated.

GeneralRe: Awesome job! Pin
Patrick Klug27-Jul-08 13:55
Patrick Klug27-Jul-08 13:55 
GeneralRe: Awesome job! Pin
Super Lloyd27-Jul-08 17:58
Super Lloyd27-Jul-08 17:58 

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.