views:

2405

answers:

3

Has anybody else noticed that Bindings with ElementName do not resolve correctly for MenuItem objects that are contained within ContextMenu objects? Check out this sample:

<Window x:Class="EmptyWPF.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"
    x:Name="window">
    <Grid x:Name="grid" Background="Wheat">
     <Grid.ContextMenu>
      <ContextMenu x:Name="menu">
       <MenuItem x:Name="menuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/>
       <MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/>
       <MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/>
       <MenuItem Header="Menu Item" Tag="{Binding ElementName=menuItem}" Click="MenuItem_Click"/>
      </ContextMenu>
     </Grid.ContextMenu>
     <Button Content="Menu" 
       HorizontalAlignment="Center" VerticalAlignment="Center" 
       Click="MenuItem_Click" Tag="{Binding ElementName=menu}"/>
     <Menu HorizontalAlignment="Center" VerticalAlignment="Bottom">
      <MenuItem x:Name="anotherMenuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/>
      <MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/>
      <MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/>
      <MenuItem Header="Menu Item" Tag="{Binding ElementName=anotherMenuItem}" Click="MenuItem_Click"/>
     </Menu>
    </Grid>
</Window>

All of the bindings work great except for the bindings contained within the ContextMenu. They print an error to the Output window during runtime.

Any one know of any work arounds? What's going on here?

+2  A: 

After experimenting a bit, I discovered one work around:

Make top level Window/UserControl implement INameScope and set NameScope of ContextMenu to the top level control.

public class Window1 : Window, INameScope
{
    public Window1()
    {
        InitializeComponent();
        NameScope.SetNameScope(contextMenu, this);
    }

    // Event handlers and etc...

    // Implement INameScope similar to this:
    #region INameScope Members

    Dictionary<string, object> items = new Dictionary<string, object>();

    object INameScope.FindName(string name)
    {
        return items[name];
    }

    void INameScope.RegisterName(string name, object scopedElement)
    {
        items.Add(name, scopedElement);
    }

    void INameScope.UnregisterName(string name)
    {
        items.Remove(name);
    }

    #endregion
}

This allows the context menu to find named items inside of the Window. Any other options?

Josh G
A: 

Context menus are tricky to bind against. They exist outside the visual tree of your control, hence they can't find your element name.

Try setting the datacontext of your context menu to its placement target. You have to use RelativeSource.

<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...
Josh
Setting the DataContext to the PlacementTarget would effect ElementName bindings? I think the DataContext is only used for Bindings that have no Source, RelativeSource, or ElementName property set.
Josh G
Setting an ElementName property will only work if the layout manager can find the associated element by navigating up the visual tree. Context menus do not exist inside the visual tree of the control to which they are added. You must set the datacontext of the context menu so the layout manager can navigate up the visual tree of its placement target to find the associated element.
Josh
Adding the DataContext to the above example didn't fix the problem. I still got the following error in the Output window: "System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=window'. BindingExpression:(no path); DataItem=null; target element is 'MenuItem' (Name='menuItem'); target property is 'Tag' (type 'Object')"
Josh G
hmmm...looking back over my code I have only done this by setting the relative source directly in the binding, I thought setting the DataContext would be simpler. Here is what worked for me:CommandTarget="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"The syntax will obviously be different, but perhaps setting the source attribute of your binding to the placement target will work?
Josh
OK, that makes sense now. You are changing the ElementName reference to a RelativeSource reference through the ContextMenu. Thanks for the thoughts.
Josh G
+9  A: 

I found a much simpler solution.

In the code behind for the UserControl:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
Josh G
This doesn't seem to work any longer in framework 4.0.
J W
Sorry, I haven't tried it on 4.0
Josh G
Actually, it works for me in 4.0.
esylvestre