This article focuses on such WPF capability as routed commands, their structure and ways of using them. Apart from this, here we will describe benefits for the developer who use routed commands and problems that the developer can encounter.
WPF-routed commands give you a specific mechanism for hooking up UI controls such as toolbar buttons and menu items to handlers without introducing a lot of tight coupling and repetitive code into your application. The difference is that, contrary to an ordinary handler hooked up to a button or timer, the routed command separates the semantics and the object that invokes a command from the logic that executes the command. Routed commands give you three main things on top of normal event handling:
- Routed command source elements (invokers) can be decoupled from command targets (handlers)—they do not need direct references to one another, as they would if they were linked by an event handler.
- Routed commands will automatically enable or disable all associated UI controls when the handler indicates the command is disabled.
- Routed commands allow you to associate keyboard shortcuts and other forms of input gestures as another way to invoke the command.
Four Main Concepts of WPF Commanding
The routed command model in WPF can be broken up into four main concepts:
- Command — the action to be executed
- Command source — the object which invokes the command
- Command target — the object that the command is being executed on
- Command binding — the object which maps the command logic to the command
Command
WPF commands are custom implementations of the ICommand interface. ICommand has the Execute and CanExecute methods and the CanExecuteChanged event. Execute performs the execution logic of the command. CanExecute determines if the command can be executed for the current command target. If true value was returned, command can be executes and method Execute() is called. Method gradually calls for paired events PreviewExecuted and Executed as tasks to complete command in all target elements using binding elements. The CanExecuteChanged event is raised if the command manager that centralizes the commanding operations detects a change in the command source that might invalidate a command that has been raised but not yet executed by the command binding.
public interface ICommand
{
event EventHandler CanExecuteChanged;
bool CanExecute(object parameter);
void Execute(object parameter);
}
The Execute and CanExecute methods of the RoutedCommand class do not contain the application logic for the command, but rather they raise routed events that tunnel and bubble through the element tree until they encounter an object with a CommandBinding. The CommandBinding contains the handlers for these events and it is the handlers that perform the command.
WPF supplies a set of common routed commands spread across several classes: MediaCommands, ApplicationCommands, NavigationCommands, ComponentCommands and EditingCommands. The classes include only the RoutedCommand objects but not the implementation logic of the command. The implementation logic is the responsibility of the object on which the command is being executed on.
Command Source
A command source is the object which invokes the command. Command sources in WPF generally implement the ICommandSource interface.
ICommandSource exposes three properties: Command, CommandTarget and CommandParameter. Command is the command to execute when the command source is invoked. CommandTarget is the object on which to execute the command. If the CommandTarget is not set, the element with keyboard focus will be the command target. CommandParameter is a user-defined data type used to pass information to the handlers implementing the command.
public interface ICommandSource
{
ICommand Command { get; }
object CommandParameter { get; }
IInputElement CommandTarget { get; }
}
Typically, a command source will listen to the CanExecuteChanged event. This event informs the command source that the ability of the command to execute on the current command target may have changed. The command source can query the current status of the routed command by using the CanExecute method. The command source can then disable itself if the command cannot execute. An example of this is a MenuItem graying itself out when a command cannot execute.
The WPF classes that implement the ICommandSource are ButtonBase, MenuItem, Hyperlink and InputBinding.
In order for an object as a command to act as a command source, it must be associated with a command. There are a few ways to accomplish this. One way is to use InputBinding.
Another way is to add the KeyGesture object to the InputGestureCollection collection of the RoutedCommand object.
Example
KeyGesture openCommandKeyGesture = new KeyGesture(Key.O,
ModifierKeys.Control);
//Alternative 1
KeyBinding openCmdKeybinding = new KeyBinding(ApplicationCommands.Open,
openCommandKeyGesture);
InputBindings.Add(openCmdKeybinding);
//Alternative 2
ApplicationCommands.Open.InputGestures.Add(openCommandKeyGesture);
KeyGesture is a class that defines a keyboard combination that can be used to invoke a command. KeyGesture is a class that defines a keyboard combination that can be used to invoke a command. The same way MouseGesture class could be used.
Command Target
The command target is the element on which the command is executed. WPF elements inherit from the System.Windows.UIElement class, which has property-collection CommandBindings of CommandBindingCollection type. This helps to make this element listening for the team. With regards to a RoutedCommand the command target is the element at which routing of the Executed and CanExecute starts. If the CommandTarget is set on an ICommandSource and the corresponding command is not a RoutedCommand, the command target is ignored.
When command calls linked to it command object the last consequently executes CanExecute() и Execute()methods of ICommand interface. These methods generate command events that move through tree of elements checking their CommandBindings collections in order to identify CommandBinding object relevant to called command. When this binding element will be identified in listening element signed for the event Executed handler will be immediately executed.
The command source can explicitly set the command target. If the command target is not defined, the element with keyboard focus will be used as the command target. One of the benefits of using the element with keyboard focus as the command target is that it allows the application developer to use the same command source to invoke a command on multiple targets without having to keep track of the command target.
For example, if a MenuItem invokes the Paste command in an application that has a TextBox control and a PasswordBox, control, the target can be either depending on which control has keyboard focus. The TextBox and PasswordBox class implementations have a built-in command binding for the Cut command and encapsulate the clipboard handling for that command (and Copy and Paste as well). The active command handler is determined by a combination of where the command invoker and command handler are in the visual tree, and where the focus is in the UI. Usually, a command invoker looks for a command binding between its own location in the visual tree and the root of the visual tree. If it finds one, the bound command handler will determine whether the command is enabled and will be called when the command is invoked. If the command is hooked up to a control inside a toolbar or menu (or, more generally, a container that sets FocusManager.IsFocusScope = true), then some additional logic runs that also looks along the visual tree path from the root to the focus element for a command binding.
The following example shows how to explicitly set the command target in markup and in code behind.
Example
CommandTarget="{Binding ElementName=mainTextBox}" />
Command Binding
A CommandBinding object associates a command with the event handlers that implement the command. The CommandBinding contains a Command property, and PreviewExecuted, Executed, PreviewCanExecute and CanExecute events.
Command is the command that the CommandBinding is being associated with. The event handlers which are attached to the PreviewExecuted and Executed events implement the command logic. The event handlers attached to the PreviewCanExecute and CanExecute events determine if the command can execute on the current command target.
Each element of logical tree can be set as listening. But to get more flexibility of command binding in could be recommended to add root element for example into window.
The following example shows how to create a CommandBinding on the root Window of an application:
Example:
CanExecute="CommandBinding_CanExecute"
Executed="CommandBinding_Executed" />
Routed Commands: Problems and Solutions
Routed commands work nicely for simple user interface scenarios, hooking up toolbar and menu items, and handling those things that are inherently coupled to the keyboard focus (such as clipboard operations). Where routed commands are insufficient, however, is when you start building complex user interfaces, where your command handling logic is in supporting code for your view definitions and your command invokers are not always inside of a toolbar or menu.
The problem when you get into that arena is that the enabling and handling logic for a command may not be part of the visual tree directly; rather, it may be located in a presenter or presentation model. Additionally, the state that determines whether the command should be enabled may have no relation to where the command invokers and views are in the visual tree. You may also have scenarios where more than one handler is valid at a given time for a particular command.
To avoid problems related to visual tree location when working with routed commands, you should try to be simple. Usually, you need to be sure that handlers for a command are in the same element or higher in the tree then an element that is calling the command.
Conclusion
To sum up, today few developers are using routed commands on a daily basis. To me, the reasons are both the lack of awareness and experience in the developer community and problems that we encounter when developing complex GUIs. However, WPF routed commands are still an elegant way to link GUL controls to handlers, and often they can help you greatly.
Denis Glazkov, Software Developer, Digital Design
For preparing this article following materials were used:
http://msdn.microsoft.com/en-us/library/ms752308.aspx
http://msdn.microsoft.com/en-us/magazine/cc785480.aspx
http://msdn.microsoft.com/en-us/library/ff650631.aspx
另一篇相关的文章:
http://blogs.msdn.com/b/ebooth/archive/2006/08/31/732609.aspx
WPF Command Scope (Focus Scope)
Rate This
30 Aug 2006 8:30 PM
My friend ran into a problem recently where they wanted to use commands such as Cut, Copy, Paste in buttons, they couldn’t figure out how to bind the buttons CommandTarget property to multiple targets. For those of you not familiar with WPF Commands check out this MSDN article.
When a command is applied to a control such as a button the focus scope of the button or it’s parent container determine if the command will apply to other controls in the Window or Page. By default Menu or ToolBar elements have their own FocusScope so controls with commands on them will work on controls outside that FocusScope.
For example the xaml snippet below shows a DockPanel with a Menu which has a MenuItem that whose Command property is assigned the Copy command. Additionally the StackPanel below the menu has a button whose Command property is also set to Copy.
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Command="Copy"/>
</Menu>
<StackPanel DockPanel.Dock="Top">
<Button Command="Copy" >Copy</Button>
</StackPanel>
<StackPanel Name="Group" DockPanel.Dock="Bottom">
<TextBox Name="MyTextBox1"/>
<TextBox Name="MyTextBox2"/>
</StackPanel>
</DockPanel>
When this code is run typing some text and then selecting that text in either TextBox in the last StackPanel will cause the MenuItem to become in enabled because it is bound to the Copy command, but the Button in the StackPanel will remain disabled. The reason for this is because the Button is in the same FocusScope as the TextBox and in WPF this will keep the Command from working with the TextBox.
To solve this problem we can add a CommandTarget property to the Button and bind to the TextBox by name. This is shown in the xaml snippet below. We add CommandTarget ="{Binding ElementName=MyTextBox1}" To the Button which causes the Button to enable when we select text in the first TextBox, but we still have a problem. We want the Copy button to work for all Text elements in our application, not just the single TextBox we have set our CommandTarget property to.
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Command="Copy"/>
</Menu>
<StackPanel DockPanel.Dock="Top">
<Button Command="Copy" CommandTarget="{Binding ElementName=MyTextBox1}">Copy</Button>
</StackPanel>
<StackPanel Name="Group" DockPanel.Dock="Bottom">
<TextBox Name="MyTextBox1"/>
<TextBox Name="MyTextBox2"/>
</StackPanel>
</DockPanel>
To solve this problem we are going to use an attached property on a class called FocusManager. The property is called IsFocusScope and is a bool. This property allows us to set the FocusScope for a control. The xaml snippet below uses the attached property on the StackPanel to set the StackPanel to its own FocusScope. This makes it so that we don’t need to set a CommandTarget, because the StackPanel with the button and the TextBoxes are now in different FocusScopes.
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Command="Copy"/>
</Menu>
<StackPanel FocusManager.IsFocusScope="True" DockPanel.Dock="Top">
<Button Command="Copy" >Copy</Button>
</StackPanel>
<StackPanel Name="Group" DockPanel.Dock="Bottom">
<TextBox Name="MyTextBox1"/>
<TextBox Name="MyTextBox2"/>
</StackPanel>
</DockPanel>
Menu and ToolBar both use the FocusManager.IsFocusScope property to set their own FocusScope and if desired you can set that property to false on a Menu or ToolBar to get the reverse behavior. The xaml snippet below does this by setting FocusManager.IsFocusScope="False" on the Menu.
<DockPanel>
<Menu FocusManager.IsFocusScope="False" DockPanel.Dock="Top">
<MenuItem Command="Copy"/>
</Menu>
<StackPanel FocusManager.IsFocusScope="True" DockPanel.Dock="Top">
<Button Command="Copy" >Copy</Button>
</StackPanel>
<StackPanel Name="Group" DockPanel.Dock="Bottom">
<TextBox Name="MyTextBox1"/>
<TextBox Name="MyTextBox2"/>
</StackPanel>
</DockPanel>