68

有没有其他人注意到带有 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>

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

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

4

6 回答 6

58

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

在 UserControl 后面的代码中:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
于 2009-06-30T20:54:54.293 回答
31

正如其他人所说,“ContextMenu”不包含在可视化树中,“ElementName”绑定不起作用。仅当上下文菜单未在“DataTemplate”中定义时,才可以按照接受的答案的建议设置上下文菜单的“NameScope”。我已经通过使用{x:Reference} 标记扩展解决了这个问题,它类似于“元素名称”绑定,但以不同方式解析绑定,绕过可视化树。我认为这比使用“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>

根据 MSDN 文档

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

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

于 2016-08-16T09:47:46.963 回答
20

这是另一个仅限 xaml 的解决方法。(这也假设你想要DataContext里面的东西,例如,你正在MVVMing它)

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

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

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

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

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

并在 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>
于 2011-03-18T19:51:20.353 回答
6

上下文菜单很难绑定。它们存在于控件的可视化树之外,因此它们找不到您的元素名称。

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

<ContextMenu 
   DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...
于 2009-06-18T18:15:49.150 回答
4

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

对顶层控件进行顶层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
}

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

于 2009-06-18T16:24:06.660 回答
1

我不确定为什么要使用魔术来避免事件处理程序中的一行代码用于您已经处理的鼠标单击:

    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;
    }
于 2011-06-18T12:36:56.643 回答