|
How to Solve Execution Problems of RoutedCommands in a WPF ContextMenu
The Problem
I recently run into a problem, with RoutedCommands in a ContextMenu. The problem was, that the commands could not be executed, even if the CommandBinding on the parent window allowed it.
The following example shows the problem with simple window that has a Menu and a ContextMenu on it. Both menus contains a MenuItem with a "Cut" command set.
<Window x:Class="RoutedCommandsInPopups.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="stack" Background="Transparent">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Cut" Command="Cut" />
</ContextMenu>
</StackPanel.ContextMenu>
<Menu>
<MenuItem Header="Edit" >
<MenuItem Header="Cut" Command="Cut" />
</MenuItem>
</Menu>
</StackPanel>
</Window>
In the codebehind of the Window I added a CommandBinding to handle the "Cut" command.
public Window1()
{
InitializeComponent();
CommandBindings.Add(
new CommandBinding(ApplicationCommands.Cut, CutExecuted, CanCut));
}
private void CutExecuted(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Cut Executed");
}
private void CanCut(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
The Reason
The reason is, that ContextMenus are separate windows with their own VisualTree and LogicalTree.
The reason is that the CommandManager searches for CommandBindings within the current focus scope. If the current focus scope has no command binding, it transfers the focus scope to the parent focus scope. When you startup your application the focus scope is not set. You can check this by calling FocusManager.GetFocusedElement(this) and you will receive null.
The Solution
Set the Logical Focus
The simplest solution is to initially set the logical focus of the parent window that is not null. When the CommandManager searches for the parent focus scope it finds the window and handels the CommandBinding correctly.
public Window1()
{
InitializeComponent();
CommandBindings.Add(
new CommandBinding(ApplicationCommands.Cut, CutExecuted, CanCut));
// Set the logical focus to the window
Focus();
}
...or the same in XAML
<Window x:Class="RoutedCommandsInPopups.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FocusManager.FocusedElement="
{Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}>
...
</Window>
Manually bind the CommandTarget
Another solution is to manually bind the CommandTarget to the parent ContextMenu.
<MenuItem Header="Cut" Command="Cut" CommandTarget="
{Binding Path=PlacementTarget,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>
Comments on this article
Show all comments
 |
| alex | |
|
| Commented on 26.January 2010 |
| very useful. thanks.
|
|
|
 |
| a | |
|
| Commented on 26.March 2010 |
| ads
|
|
|
 |
| speed | |
|
| Commented on 20.April 2010 |
| Now that is the best idea I've read all day. Thank you for posting it.
|
|
|
 |
| Dario | |
|
| Commented on 21.April 2010 |
Super!
thanks
|
|
|
 |
| Ed | |
|
| Commented on 5.May 2010 |
| At last! That's what I was looking for.
|
|
|
 |
| Andreas | |
|
| Commented on 28.July 2010 |
| Thank you for that, saved me a lot of time.
|
|
|
|