I recently ran into a few problems with RoutedCommands in a ContextMenu I was adding to an application. The problem was that when the ContextMenu was opened it wasn’t always finding the command bindings that I had in the parent Window (it would sometimes) and so the MenuItem was disabled. After searching the web for some answers I found a few people that had also noticed this problem and they had a proposed work around. You can find the work around here. However the work around provided didn’t fit well with how I was setting up the ContextMenu so I decided to dig a little deeper. After about 8 hours I found the reason and a solution to the problem that is far easier (at least for me) to put in place.
Before we go too far though I’ll provide some code so that you can replicate the issue for your self. If you start with a blank WPF application project called RoutedCommandInContextMenu then you can simply add the following code to the listed files.
Add to Window1.xaml
<Window x:Class="RoutedCommandInContextMenu.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RoutedCommandInContextMenu"
Title="Window1"
Height="300"
Width="300">
<ScrollViewer>
<TextBlock Text="fooooobaaaaaar">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Foo"
Command="{x:Static local:MyCommands.FooBar}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</ScrollViewer>
</Window>
Add to Window1.cs
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
CommandBindings.Add(new CommandBinding(MyCommands.FooBar, FooExecuted, CanFooExecute));
}
public void FooExecuted(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Foo!");
}
public void CanFooExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
public static class MyCommands
{
public static RoutedCommand FooBar = new RoutedCommand();
}
If you then run this application and right click (without doing anything else first) you will see that the “Foo” menu item is disabled in the context menu. You will also note that if you left click the non border area of the window and then right click again the “Foo” menu item is enabled.
When I first noticed this behaviour I thought that perhaps the MenuItem was simply not calling the CanExecute method of the command. So I made a simple WrappedCommand class that implemented ICommand and used that as the command instead. I was then able to put a break in the can execute method of my wrapped command and see that it was indeed being called. I could also see that the underlying routed command was returning false for its CanExecute even though the Window’s command binding was never being called. Clearly there is something going on with the routing of the command.
ContextMenus are implemented as a separate window which is managed by the Popup class. Because of this they have a different Visual Tree. Their Logical Tree is also separate from the Window they were launched from. There are one or two situations where it looks like the Popup might report itself to be a child of the launching Window, but it doesn’t in the case of our ContextMenu (even when it is working in our example).
It turns out that in when a UIElement is determining if a routed command can execute or not it uses an internal method on the CommandManager class called OnCanExecute. In here we can see (with enough analasis) that the method searches for a command binding within the focus scope of the sender (the element that raised the event), then if it can’t find one it will transfer the event to the parent focus scope (assuming there is one) to see if it has any CommandBindings registered. This is how the routed command execution travels from the ContextMenu to the Window when things are working. The thing is that it will start routing the event in the parent scope from whatever has the logical focus in that scope, this is where our problem lies. Until we click the window (actually the ScrollViewer to be precise) the current logical focus is null, which means that the CommandManager doesn’t know where to start routing the event in the parent focus scope so it doesn’t transfer the event. Once we click the ScrollViewer the logical focus scope is set to the ScrollViewer so the CommandManager transfers execution of the routed event to the scroll viewer which then finds the Window’s command bindings and everything is dandy.
To fix this issue you can simply add a call to Focus() in the Window’s constructor like so:
public Window1()
{
InitializeComponent();
CommandBindings.Add(new CommandBinding(MyCommands.FooBar, FooExecuted, CanFooExecute));
Focus();
// Alternativly you could use
// SetValue(FocusManager.FocusedElementProperty, this);
// Another alternative is to use the following on the Window element in XAML
// FocusManager.FocusedElement="{Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}"
}
I believe that this is a bug in the framework as the documentation on Logical Focus states the following:
An element that has keyboard focus will also have logical focus for the focus scope it belongs to; therefore, setting focus on an element with the Focus method on the Keyboard class or the base element classes will attempt to give the element keyboard focus and logical focus.
You can demonstrate that this statement in the documentation is correct by adding the following code to the Window1 class and commenting out the Focus() call in the constructor.
protected override void OnContextMenuOpening(ContextMenuEventArgs e)
{
Debug.Assert(Keyboard.FocusedElement != this ||
Keyboard.FocusedElement == FocusManager.GetFocusedElement(this));
base.OnContextMenuOpening(e);
}
Now when you right click the window the assertion will fail, because FocusManager.GetFocusedElement() is returning null, but the Keyboard is saying that the Window has the focus.
So there you go, after a lot of stepping through framework code, pulling my hair out and staying up into the wee hours of the morning, I was finally able to figure out what was going on. I have raised this as a bug on MS connect so if you would like to rate it that might be nice. The bug can be found here: https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=415864&SiteID=212.
Also when writing this post I found the following article on WPF command routing which explains a few other interesting uses for focus scopes which I found interesting: http://www.aspfree.com/c/a/.NET/More-on-Commands-Input-and-the-WPF/2/