wpf 来自 ContextMenu 中 MenuItem 的 ElementName 绑定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1013558/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:35:32  来源:igfitidea点击:

ElementName Binding from MenuItem in ContextMenu

wpfdata-bindingcontextmenuelementname

提问by Josh G

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

有没有其他人注意到带有 ElementName 的绑定不能正确解析MenuItem对象中包含的ContextMenu对象?查看此示例:

<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.

除了 ContextMenu 中包含的绑定之外,所有绑定都可以很好地工作。它们在运行时将错误打印到输出窗口。

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

任何人都知道任何解决方法?这里发生了什么?

采纳答案by Josh G

I found a much simpler solution.

我找到了一个更简单的解决方案。

In the code behind for the UserControl:

在 UserControl 后面的代码中:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));

回答by Marc

As said by others, the 'ContextMenu' is not contained in the visual tree and an 'ElementName' binding won't work. Setting the context menu's 'NameScope' as suggested by the accepted answer only works if the context menu is not defined in a 'DataTemplate'. I have solved this by using the {x:Reference} Markup-Extensionwhich is similar to the 'ElementName' binding but resolves the binding differently, bypassing the visual tree. I consider this to be far more readable than using 'PlacementTarget'. Here is an example:

正如其他人所说,“ContextMenu”不包含在可视化树中,“ElementName”绑定不起作用。仅当上下文菜单未在“DataTemplate”中定义时,按照接受的答案的建议设置上下文菜单的“NameScope”才有效。我通过使用{x:Reference} 标记扩展解决了这个问题,它类似于 'ElementName' 绑定,但以不同的方式解析绑定,绕过可视化树。我认为这比使用“PlacementTarget”更具可读性。下面是一个例子:

<Image Source="{Binding Image}">       
    <Image.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Delete" 
                      Command="{Binding Source={x:Reference Name=Root}, Path=DataContext.RemoveImage}"
                      CommandParameter="{Binding}" />
        </ContextMenu>
    </Image.ContextMenu>
</Image>

According to the MSDN-documentation

根据 MSDN 文档

x:Reference is a construct defined in XAML 2009. In WPF, you can use XAML 2009 features, but only for XAML that is not WPF markup-compiled. Markup-compiled XAML and the BAML form of XAML do not currently support the XAML 2009 language keywords and features.

x:Reference 是 XAML 2009 中定义的一种构造。在 WPF 中,您可以使用 XAML 2009 功能,但仅限于未经过 WPF 标记编译的 XAML。标记编译的 XAML 和 XAML 的 BAML 形式目前不支持 XAML 2009 语言关键字和功能。

whatever that means... Works for me, though.

不管这意味着什么......不过对我有用。

回答by Marc

Here's another xaml-only workaround. (This also assumes you want what's inside the DataContext, e.g., you're MVVMingit)

这是另一个仅 xaml 的解决方法。(这也假设您想要DataContext 中的内容,例如,您正在对它进行 MVVMing

Option one, where the parent element of the ContextMenuis not in a DataTemplate:

选项一,其中ContextMenu的父元素不在DataTemplate 中

Command="{Binding PlacementTarget.DataContext.MyCommand, 
         RelativeSource={RelativeSource AncestorType=ContextMenu}}"

This would work for OP's question. This won't work if you are inside of a DataTemplate. In these cases, the DataContextis often one of many in a collection, and the ICommandyou wish to bind to is a sibling property of the collection within the same ViewModel (the DataContextof the Window, say).

这适用于 OP 的问题。如果您在DataTemplate内,这将不起作用。在这些情况下,DataContext通常是集合中的一个,而您希望绑定到的ICommand是同一 ViewModel(例如,Window的DataContext)中集合的同级属性。

In these cases, you can take advantage of the Tagto temporarily hold the parent DataContextwhich contains both the collection AND your ICommand:

在这些情况下,您可以利用Tag临时保存包含集合和您的 ICommand的父DataContext

class ViewModel
{
    public ObservableCollection<Derp> Derps { get;set;}
    public ICommand DeleteDerp {get; set;}
} 

and in the xaml

并在 xaml 中

<!-- ItemsSource binds to Derps in the DataContext -->
<StackPanel
    Tag="{Binding DataContext, ElementName=root}">
    <StackPanel.ContextMenu>
        <ContextMenu>
            <MenuItem
                Header="Derp"                       
                Command="{Binding PlacementTarget.Tag.DeleteDerp, 
                RelativeSource={RelativeSource 
                                    AncestorType=ContextMenu}}"
                CommandParameter="{Binding PlacementTarget.DataContext, 
                RelativeSource={RelativeSource AncestorType=ContextMenu}}">
            </MenuItem>

回答by Josh

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.

尝试将上下文菜单的数据上下文设置为其放置目标。你必须使用RelativeSource。

<ContextMenu 
   DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...

回答by Josh G

After experimenting a bit, I discovered one work around:

经过一番试验,我发现了一种解决方法:

Make top level Window/UserControlimplement INameScopeand set NameScopeof ContextMenuto the top level control.

制作顶层Window/UserControl实施INameScope和组NameScopeContextMenu顶端水平的控制。

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?

这允许上下文菜单在Window. 还有其他选择吗?

回答by Marino ?imi?

I'm not sure why resort to magic tricks just to avoid a one line of code inside the eventhandler for the mouse click you already handle:

我不知道为什么诉诸魔术只是为了避免事件处理程序中的一行代码用于您已经处理的鼠标单击:

    private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        // this would be your tag - whatever control can be put as string intot he tag
        UIElement elm = Window.GetWindow(sender as MenuItem).FindName("whatever control") as UIElement;
    }