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:
<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>
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 EditingCommand
s 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
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
(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 CommandTarget
s 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:
Command
s 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 CommandSource
s target different CommandTarget
s, it doesn’t work anymore.
- If you go down this route, you cannot use any of the already provided
Command
s 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:
- See if the Command can execute on the target
- 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 CommandSource
s 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.
- See if the Command can execute on the target and more!
- 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
)).
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
.
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.
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)
{
if (canExecuteChangedHandler == null)
canExecuteChangedHandler = OnCanExecuteChanged;
newValue.CanExecuteChanged += canExecuteChangedHandler;
}
else
canExecuteChangedHandler = null;
}
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.
private void UpdateCanExecute()
{
if (IsCommandExecuting)
return;
IsCommandExecuting = true;
try
{
var parameter = new CommandCanExecuteParameter(null);
CommandUtil.CanExecute(this, parameter);
{
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!
<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 CommandBinding
s 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 CommandBinding
s. As you can see, I spent hours on the look and feel:
<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[^]).