38

Let's say I have a Window with a property returning a Command (in fact, it's a UserControl with a Command in a ViewModel class, but let's keep things as simple as possible to reproduce the problem).

The following works:

<Window x:Class="Window1" ... x:Name="myWindow">
    <Menu>
        <MenuItem Command="{Binding MyCommand, ElementName=myWindow}" Header="Test" />
    </Menu>
</Window>

But the following does not work.

<Window x:Class="Window1" ... x:Name="myWindow">
    <Grid>
        <Grid.ContextMenu>
            <ContextMenu>
                <MenuItem Command="{Binding MyCommand, ElementName=myWindow}" Header="Test" />
            </ContextMenu>            
        </Grid.ContextMenu>
    </Grid>
</Window>

The error message I get is

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myWindow'. BindingExpression:Path=MyCommand; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

Why? And how do I fix this? Using the DataContext is not an option, since this problem occurs way down the visual tree where the DataContext already contains the actual data being displayed. I already tried using {RelativeSource FindAncestor, ...} instead, but that yields a similar error message.

Heinzi
  • 167,459
  • 57
  • 363
  • 519

6 Answers6

75

The problem is that the ContextMenu it not in the visual tree, so you basically have to tell the Context menu about which data context to use.

Check out this blogpost with a very nice solution of Thomas Levesque.

He creates a class Proxy that inherits Freezable and declares a Data dependency property.

public class BindingProxy : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

Then it can be declared in the XAML (on a place in the visual tree where the correct DataContext is known):

<Grid.Resources>
    <local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</Grid.Resources>

And used in the context menu outside the visual tree:

<ContextMenu>
    <MenuItem Header="Test" Command="{Binding Source={StaticResource Proxy}, Path=Data.MyCommand}"/>
</ContextMenu>
Daniel
  • 1,044
  • 11
  • 10
  • 1
    This __finally__ worked after I tried about 10 different approaches (from SO and elsewhere). Thanks a lot for this clean and pretty simple, yet so awesome answer! :) – Yoda Oct 19 '16 at 15:55
  • This is the **best solution** – n00b101 Dec 27 '16 at 07:04
  • 3
    Thats a very nice solution. I make my binding proxies strongly typed (Data property and dependency property are not typeof(object) but typeof(MyViewModel). This way there is better intellisense where I have to bind via the proxy. – Michael Nov 07 '17 at 21:41
18

Hurray for web.archive.org! Here is the missing blog post:

Binding to a MenuItem in a WPF Context Menu

Wednesday, October 29, 2008 — jtango18

Because a ContextMenu in WPF does not exist within the visual tree of your page/window/control per se, data binding can be a little tricky. I have searched high and low across the web for this, and the most common answer seems to be “just do it in the code behind”. WRONG! I didn’t come in to the wonderful world of XAML to be going back to doing things in the code behind.

Here is my example to that will allow you to bind to a string that exists as a property of your window.

public partial class Window1 : Window
{
    public Window1()
    {
        MyString = "Here is my string";
    }

    public string MyString
    {
        get;
        set;

    }
}

    <Button Content="Test Button" Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
        <Button.ContextMenu>
            <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" >
                <MenuItem Header="{Binding MyString}"/>
            </ContextMenu>
        </Button.ContextMenu>
    </Button>

The important part is the Tag on the button(although you could just as easily set the DataContext of the button). This stores a reference to the parent window. The ContextMenu is capable of accessing this through it’s PlacementTarget property. You can then pass this context down through your menu items.

I’ll admit this is not the most elegant solution in the world. However, it beats setting stuff in the code behind. If anyone has an even better way to do this I’d love to hear it.

N_A
  • 19,799
  • 4
  • 52
  • 98
  • Oddly enough, I had set the `DataContext` of the `MenuItem` and it doesn't work. As soon as I changed it to be set on the `ContextMenu` as you've described, it began to work. Thanks for posting this. – Nicholas Miller Nov 03 '15 at 18:30
11

I found out it wasn't working for me due to the menu item being nested, which mean I had to traverse up an extra "Parent" to find the PlacementTarget.

A better way is to find the ContextMenu itself as the RelativeSource and then just bind to the placement target of that. Also since the tag is the window itself, and your command is in the viewmodel, you need to have the DataContext set as well.

I ended up with something like this

<Window x:Class="Window1" ... x:Name="myWindow">
...
    <Grid Tag="{Binding ElementName=myWindow}">
        <Grid.ContextMenu>
            <ContextMenu>
                <MenuItem Command="{Binding PlacementTarget.Tag.DataContext.MyCommand, 
                                            RelativeSource={RelativeSource Mode=FindAncestor,                                                                                         
                                                                           AncestorType=ContextMenu}}"
                          Header="Test" />
            </ContextMenu>
        </Grid.ContextMenu>
    </Grid>
</Window>

What this means is that if you end up with a complicated context menu with submenus etc.. you don't need to keep adding "Parent" to each levels Commands.

-- EDIT --

Also came up with this alternative to set a tag on every ListBoxItem that binds to the Window/Usercontrol. I ended up doing this because each ListBoxItem was represented by their own ViewModel but I needed the menu commands to execute via the top level ViewModel for the control, but pass their the list ViewModel as a parameter.

<ContextMenu x:Key="BookItemContextMenu" 
             Style="{StaticResource ContextMenuStyle1}">

    <MenuItem Command="{Binding Parent.PlacementTarget.Tag.DataContext.DoSomethingWithBookCommand,
                        RelativeSource={RelativeSource Mode=FindAncestor,
                        AncestorType=ContextMenu}}"
              CommandParameter="{Binding}"
              Header="Do Something With Book" />
    </MenuItem>>
</ContextMenu>

...

<ListView.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="ContextMenu" Value="{StaticResource BookItemContextMenu}" />
        <Setter Property="Tag" Value="{Binding ElementName=thisUserControl}" />
    </Style>
</ListView.ItemContainerStyle>
nrjohnstone
  • 778
  • 10
  • 17
8

Based on HCLs answer, this is what I ended up using:

<Window x:Class="Window1" ... x:Name="myWindow">
    ...
    <Grid Tag="{Binding ElementName=myWindow}">
        <Grid.ContextMenu>
            <ContextMenu>
                <MenuItem Command="{Binding Parent.PlacementTarget.Tag.MyCommand, 
                                            RelativeSource={RelativeSource Self}}"
                          Header="Test" />
            </ContextMenu>
        </Grid.ContextMenu>
    </Grid>
</Window>
Community
  • 1
  • 1
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • 1
    Does this actually work? I've been trying to get this working, and using snoop it seems that the Command gets evaluated once and never actually updated. PlacementTarget is null until the context menu is actually activated, at which point Parent.PlacementTarget.Tag is valid but the Command never gets dynamically updated (from what I can see in Snoop) – nrjohnstone Feb 19 '14 at 02:21
  • this is actually the only thing that works for me and i've tried like 10-15 suggestions from all over this site. – UяošKoт Apr 12 '15 at 11:08
2

If (like me) you have an aversion to ugly complex binding expressions, here is a simple code-behind solution to this problem. This approach still allows you to keep clean command declarations in your XAML.

XAML:

<ContextMenu ContextMenuOpening="ContextMenu_ContextMenuOpening">
    <MenuItem Command="Save"/>
    <Separator></Separator>
    <MenuItem Command="Close"/>
    ...

Code behind:

private void ContextMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
    foreach (var item in (sender as ContextMenu).Items)
    {
        if(item is MenuItem)
        {
           //set the command target to whatever you like here
           (item as MenuItem).CommandTarget = this;
        } 
    }
}
Tom Makin
  • 3,203
  • 23
  • 23
2

Answer in 2020:

I'm leaving this answer here for anyone else who googled this question, as this is the first search result that shows up. This worked for me and is simpler than the other suggested solutions:

<MenuItem Command="{Binding YourCommand}" CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>

As described here:

https://wpf.2000things.com/2014/06/19/1097-getting-items-in-context-menu-to-correctly-use-command-binding/

Psykojello
  • 49
  • 4