« Extracting thumbnails from a JPEG/JPG in .NET 3.0 | Main | Disposing Virtualizing Stack Panel »

CommandTarget, MenuItem, ContextMenus, and Binding, oh my!

MenuItems can be bound directly to a command. The MenuItem is enabled or disabled based on several factors, including:

  • Does a control have focus?
  • Does the Command have a CommandTarget specified?
  • Does the CanExecute on the CommandBinding return True?

All in all -- not too bad.

That allows me to do something like this:

  <Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.Close" 
CanExecute="AlwaysCanExecute"
Executed="ApplicationCommandsClose" /> </Window.CommandBindings> <Window.InputBindings> <KeyBinding Key="Escape" Command="ApplicationCommands.Close" /> </Window.InputBindings> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Menu> <MenuItem Header="_File"> <MenuItem Command="ApplicationCommands.Close" > <MenuItem.Header> Exit </MenuItem.Header> </MenuItem> </MenuItem> </Menu>
    private void AlwaysCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
      e.CanExecute = true;
    }

private void ApplicationCommandsClose(object sender, ExecutedRoutedEventArgs e)
{
Close();
}


I always want the Exit menu enabled (at least here), so I've created a method called "AlwaysCanExecute" that always returns True.


Trouble happens though if I start using a ContextMenu on a control that does not get focus. In this example an Image:

    <Image Width="128" Height="128" Source="myimage.png" Stretch="Fill">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Close">
<MenuItem.Header>
Exit
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</Image.ContextMenu>
</Image>

Here I've added a context menu to an image. If I run this application, the MenuItem for Exit is always disabled. The Window CommandBindings are still set.


There seem to be two work arounds.


The first thing I tried though was to use a Binding syntax to set the CommandTarget to a particular element.

CommandTarget="{Binding ElementName=MainWindow}"

The error message that happens is this:



System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=MainWindow'. BindingExpression:(no path); DataItem=null; target element is 'MenuItem' (Name='mnuClose'); target property is 'CommandTarget' (type 'IInputElement')


This really should have work. I think it's a bug (but would like to hear differently).


Oddly, if I, using this:


 

<MenuItem Command="ApplicationCommands.Close" x:Name="mnuClose" 
CommandTarget="{Binding ElementName=MainWindow}" >
<MenuItem.Header>
Exit
</MenuItem.Header>
</MenuItem>

And then in code, set the CommandTarget explicitly:

    public Window1()
{
InitializeComponent();
mnuClose.CommandTarget = this;
}

It works fine. So, the Binding is the problem. That's solution one. If you don't mind adding code and giving the context menuitems a name, you're good to go.


However, I wasn't satisfied.


It just can't seem to find the right scope to locate the elements (hence the error). I tried many Binding combinations and couldn't seem to find one that worked.


So, in the hunt to find an alternative, I discovered the easiest work around that involves zero code is to duplicate the CommandBindings like this:

      <Image.ContextMenu>
<ContextMenu>
<ContextMenu.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
CanExecute="AlwaysCanExecute"
Executed="ApplicationCommandsClose" />
</ContextMenu.CommandBindings>
<MenuItem Command="ApplicationCommands.Close" >
<MenuItem.Header>
Exit
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</Image.ContextMenu>

I've added the Commands (by adding a Binding) that I'm using in the ContextMenu directly to the ContextMenu CommandBindings property. I'm calling the same methods as I called for the Window.CommandBindings (as the logic of when and what to do hasn't changed). It's not elegant as I'd like and I suppose I could have written a helper class to guarantee the command bindings have been properly duplicated, but ... that's left as an exercise to the reader!


I like setting the CommandBindings in the application level so this bugs me that I need to duplicate them and keep them up to date. I can't imagine that Microsoft would declare this anything but a bug. If someone knows better leave a comment if they're enabled or send me an e-mail to coder @ this website.


If the control can get focus, this all just works as expected without any trickery. Enjoy.

Comments

Thanks for this, I've just started with WPF and was looking to do exactly what you've done here - last thing on a Friday it's nice to read something that explains things simply!

This was driving me crazy. Thanks for posting this.

Did you ever get feedback from Microsoft to why the commandbindings need to be in the contextmenu scope for it to work?

Nope -- other than I now know that because the contextmenu isn't in the logical or virtual tree really -- it isn't wired up correctly. It sounds like they intend to MAYBE fix that in an upcoming version (as they fixed some other places like this in V1).

Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).