Introduction
This article explains how to embed some intelligence into a routed command. That intelligence can be used as a fallback plan, in case no element in your UI knows what to do when the command is executed. It also allows you to encapsulate the most common command execution logic into the command itself, which makes it easier to reuse.
Background
The command system in WPF is based on the ICommand interface. Any object which implements that interface can be treated as a command. You can create a type which implements the interface and contains logic to determine if the command can be executed, and what to do when it is executed. This provides WPF developers with a convenient mechanism for encapsulating an action, and allowing it to be used by WPF.
Often the meaning of executing a command can change based on runtime context; such as whether some property on a domain object returns a specific value, or if a CheckBox
is checked, etc. In situations like this it does not always make sense to encapsulate your logic in a command object, because it might need to have intimate knowledge of the UI in which the command was executed.
That is why WPF has the routed command system. It allows for the actual execution logic of a command to be delegated to an arbitrary node in the element tree. By bubbling a certain routed event up the element tree when a routed command is executed, it allows any element in the tree to say, "Hey, I know what to do when this command is executed. I'll take care of it." Paradoxically, routed commands are very useful because they don't do anything. All that they do is send a notification through the element tree when they are executed, letting others have a chance to perform their own version of the command's execution logic (as well as letting others decide if the command can execute at all).
To learn more about the command system in WPF, read this page in the SDK.
The Problem
Suppose that I am creating a command which displays a Web page. Most of the time, I would expect the Web page to be displayed in the user's default Web browser. I do not want to require that the page is displayed in any particular way, so the command should be a routed command. That will allow the UI in which the command is used to determine how to display a Web page, perhaps in a Frame
element in the application. Since our command might be used in many UIs, and even many applications, it is important that we offer this flexibility.
Now we have arrived at an interesting problem. We want the command to be routed, so that it can be used in a flexible manner, but we also want to provide standard execution logic which opens a Web page in the user's default browser. The standard execution logic should be used as a fallback plan if the routed command was not handled by any element in the tree.
Unfortunately the RoutedCommand
class does not expose any virtual methods that we can override to specify our default execution logic. We cannot simply subclass RoutedCommand
, override a couple of methods, and be done with it.
How can we create a routed command which has "default" behavior?
The Solution
I created a subclass of RoutedCommand
called SmartRoutedCommand
. That class exposes an attached Boolean
property called IsCommandSink
. When you are using a SmartRoutedCommand
in your UI, you must set IsCommandSink
to true
on the root element in the element tree. Doing so enables your SmartRoutedCommand
subclasses to perform their default execution logic when routed command execution notifications are unhandled by the element tree.
The architects behind WPF's routed event system must have eaten "hot peppers that were grown deep in the jungle primeval by the inmates of a Guatemalan insane asylum". They made routed events so insanely flexible that it allows us to solve difficult problems like this with very few lines of code. Here is the entire SmartRoutedCommand
class. Pay close attention to the OnIsCommandSinkChanged
method.
public abstract class SmartRoutedCommand : RoutedCommand
{
#region IsCommandSink
public static bool GetIsCommandSink(DependencyObject obj)
{
return (bool)obj.GetValue(IsCommandSinkProperty);
}
public static void SetIsCommandSink(DependencyObject obj, bool value)
{
obj.SetValue(IsCommandSinkProperty, value);
}
public static readonly DependencyProperty IsCommandSinkProperty =
DependencyProperty.RegisterAttached(
"IsCommandSink",
typeof(bool),
typeof(SmartRoutedCommand),
new UIPropertyMetadata(false, OnIsCommandSinkChanged));
static void OnIsCommandSinkChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
bool isCommandSink = (bool)e.NewValue;
UIElement sinkElem = depObj as UIElement;
if (sinkElem == null)
throw new ArgumentException("Target object must be a UIElement.");
if (isCommandSink)
{
CommandManager.AddCanExecuteHandler(sinkElem, OnCanExecute);
CommandManager.AddExecutedHandler(sinkElem, OnExecuted);
}
else
{
CommandManager.RemoveCanExecuteHandler(sinkElem, OnCanExecute);
CommandManager.RemoveExecutedHandler(sinkElem, OnExecuted);
}
}
#endregion
#region Static Callbacks
static void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
SmartRoutedCommand cmd = e.Command as SmartRoutedCommand;
if (cmd != null)
{
e.CanExecute = cmd.CanExecuteCore(e.Parameter);
}
}
static void OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
SmartRoutedCommand cmd = e.Command as SmartRoutedCommand;
if (cmd != null)
{
cmd.ExecuteCore(e.Parameter);
e.Handled = true;
}
}
#endregion
#region Abstract Methods
protected abstract bool CanExecuteCore(object parameter);
protected abstract void ExecuteCore(object parameter);
#endregion
}
The fundamental idea here is that when the IsCommandSink
attached property is set on an element, we add a handler to the CommandManager
's CanExecute
and Executed
attached events. Those events are bubbled up the element tree when a routed command is being queried to see if it can execute and when it has been executed, respectively.
When those events are raised on that element the event handling methods in SmartRoutedCommand
are invoked, allowing us to then call the abstract methods which subclasses override to provide their default execution logic. This technique is like having the root of the element tree forward our command a notification that it is being used but nobody knows what to do, so that we can then use the subclass's default logic.
The Demo App
This article is accompanied by a demo application which shows how to use SmartRoutedCommand
. The demo app lets you type in some keywords and then click a button to search Google with those words. It contains a command called OpenWebPageCommand
, which derives from SmartRoutedCommand
. The UI allows you to either open a custom Web browser (which is created by the demo app's Window
), or to use your default Web browser (which is opened by the command itself).
Below is a screenshot of the demo app:
Here is OpenWebPageCommand
:
public class OpenWebPageCommand : SmartRoutedCommand
{
public static readonly ICommand Instance = new OpenWebPageCommand();
private OpenWebPageCommand() { }
protected override bool CanExecuteCore(object parameter)
{
string uri = parameter as string;
if (uri == null)
return false;
bool isUriValid = Uri.IsWellFormedUriString(uri, UriKind.Absolute);
bool haveConnection = NetworkInterface.GetIsNetworkAvailable();
return isUriValid && haveConnection;
}
protected override void ExecuteCore(object parameter)
{
string uri = parameter as string;
Process.Start(uri);
}
}
The XAML for the Window
which uses that command is seen below. Notice how the IsCommandSink
property is set on the Window
, making it possible for the OpenWebPageCommand
's default execution logic to be used.
<Window
x:Class="SmartRoutedCommandDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SmartRoutedCommandDemo"
Title="SmartRoutedCommand Demo"
FontSize="12"
Width="300" Height="140"
WindowStartupLocation="CenterScreen"
local:SmartRoutedCommand.IsCommandSink="True"
>
<StackPanel Margin="2">
<StackPanel.Resources>
<local:KeywordsToGoogleSearchConverter x:Key="googleSearchConv" />
</StackPanel.Resources>
<StackPanel.CommandBindings>
<CommandBinding
Command="{x:Static local:OpenWebPageCommand.Instance}"
CanExecute="OnCanCmdExecute"
Executed="OnCmdExecuted"
/>
</StackPanel.CommandBindings>
<TextBlock Text="Enter Keywords:" />
<TextBox x:Name="txtKeywords" Margin="0,4" />
<Button
Command="{x:Static local:OpenWebPageCommand.Instance}"
CommandParameter="{Binding
Converter={StaticResource googleSearchConv},
ElementName=txtKeywords,
Mode=OneWay,
Path=Text}"
HorizontalAlignment="Right"
IsDefault="True"
>
Google It!
</Button>
<CheckBox
x:Name="chkUseCustomBrowser"
IsChecked="False"
Margin="0,20,0,0"
>
Use Custom Web Browser
</CheckBox>
</StackPanel>
</Window>
Notice that the StackPanel
has a CommandBinding
for the OpenWebPageCommand
. To determine if the StackPanel
should handle the command execution, the following method is invoked by the WPF commanding system at various times:
void OnCanCmdExecute(object sender, CanExecuteRoutedEventArgs e)
{
bool useCustomBrowser =
this.chkUseCustomBrowser.IsChecked.GetValueOrDefault();
if(useCustomBrowser)
{
e.CanExecute = true;
}
}
If the CanExecute
property of the event argument is not set to true
, then the CanExecute
routed event keeps bubbling up the element tree until eventually the Window
forwards the notification over to the OpenWebPageCommand
itself. At that point the command's built-in logic will be used.
Conclusion
By using the SmartRoutedCommand
class, you can have the best of both worlds; routed commands with default execution logic. This technique is not always appropriate, since some commands do not have a reasonable "default action". But if you find yourself implementing the same command execution logic many times for the same routed command, consider using SmartRoutedCommand
to consolidate that logic into one convenient place.