s.ermakovich
s.ermakovich

Reputation: 2674

How to bind MenuItem.Header to Window/UserControl dependency property?

I`m wondering how can I bind MenuItem.Header to the parent Window/UserControl dependency property? Here is a simple example:

Window1.xaml:

<Window x:Class="WpfApplication1.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="self">
    <Grid>
        <Grid.ContextMenu>
            <ContextMenu>
                <MenuItem Header="{Binding Path=MenuText, ElementName=self}" />
            </ContextMenu>
        </Grid.ContextMenu>
        <TextBlock Text="{Binding Path=MenuText, ElementName=self}"/>
    </Grid>
</Window>

Window1.xaml.cs:

public partial class Window1 : Window {
    public static readonly DependencyProperty MenuTextProperty = DependencyProperty.Register(
        "MenuText", typeof (string), typeof (Window1), new PropertyMetadata("Item 1"));

    public Window1()
    {
        InitializeComponent();
    }

    public string MenuText {
        get { return (string)this.GetValue(MenuTextProperty); }
        set { this.SetValue(MenuTextProperty, value); }
    }
}

In my case, textblock displays "Item 1", and context menu displays empty item. What I`m doing wrong? It seems for me, that I faced serious misunderstaning of WPF databinding principles.

Upvotes: 4

Views: 6884

Answers (2)

brunnerh
brunnerh

Reputation: 185489

You should see this in the Output window of Visual Studio:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=self'. BindingExpression:Path=MenuText; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Header' (type 'Object')

That is because the ContextMenu is disconnected from the VisualTree, you need to do this Binding differently.

One way is via ContextMenu.PlacementTarget (which should be the Grid), you could use its DataContext to establish a binding, e.g.:

<MenuItem Header="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext.MenuText}"/>

or set up the DataContext in the ContextMenu itself:

<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}">
    <MenuItem Header="{Binding Path=MenuText}"/>
</ContextMenu>

If this is not an option (because the DataContext of the Grid cannot be the Window/UserControl) you can try to pass the reference to the Window/UserControl through the Tag of your Grid for example.

<Grid ...
      Tag="{x:Reference self}">
    <Grid.ContextMenu>
        <!-- The DataContext is now bound to PlacementTarget.Tag -->
        <ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Tag}">
            <MenuItem Header="{Binding Path=MenuText}"/>
        </ContextMenu>
    ...

As a side-note: Because of this behavior i tend to define a helper-style in App.xaml to make all ContextMenus "pseudo-inherit" the DataContext from their parent:

    <!-- Context Menu Helper -->
    <Style TargetType="{x:Type ContextMenu}">
        <Setter Property="DataContext" Value="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"/>
    </Style>

Upvotes: 8

Wallstreet Programmer
Wallstreet Programmer

Reputation: 9687

Alternative to H.B.'s solution is this attached behavior: ContextMenuServiceExtensions.DataContext Attached Property

Upvotes: 1

Related Questions