C# WPF:将上下文菜单绑定到MVVM命令

C# WPF:将上下文菜单绑定到MVVM命令,c#,wpf,data-binding,xaml,mvvm,C#,Wpf,Data Binding,Xaml,Mvvm,假设我有一个属性返回命令的窗口(事实上,它是一个UserControl,在ViewModel类中有一个命令,但是让我们尽可能简单地重现问题) 以下工作: <Window x:Class="Window1" ... x:Name="myWindow"> <Menu> <MenuItem Command="{Binding MyCommand, ElementName=myWindow}" Header="Test" /> <

假设我有一个属性返回命令的窗口(事实上,它是一个UserControl,在ViewModel类中有一个命令,但是让我们尽可能简单地重现问题)

以下工作:

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

但以下方法不起作用

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

我收到的错误消息是

System.Windows.Data错误:4:找不到引用为“ElementName=myWindow”的绑定源。BindingExpression:Path=MyCommand;DataItem=null;目标元素是“MenuItem”(名称=“”);目标属性为“Command”(类型为“ICommand”)

为什么??我该如何解决这个问题?使用
DataContext
不是一个选项,因为此问题发生在可视化树的最下面,其中DataContext已经包含正在显示的实际数据。我已经尝试过使用
{relativeSourceFindancestor,…}
,但这会产生类似的错误消息。

有关解决方法,请参阅Justin Taylor的文章

更新
遗憾的是,被引用的博客不再可用。我试图用另一个答案来解释这一过程。可以找到它。

有关解决方法,请参阅贾斯汀·泰勒的文章

更新
遗憾的是,被引用的博客不再可用。我试图用另一个答案来解释这一过程。可以找到它。

基于,这就是我最终使用的:

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

...
基于这一点,我最终使用了:

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

...
万岁!这是:

绑定到WPF上下文菜单中的菜单项

2008年10月29日星期三-18

因为WPF中的上下文菜单不存在于 就页面/窗口/控件本身而言,数据绑定可能有点棘手。 我在网上到处寻找这个,而且最 常见的答案似乎是“只需在代码背后做”。错了!我 没有回到XAML的精彩世界 在代码后面做事情

下面是我的示例,它将允许您绑定到 作为窗口的属性存在

公共部分类窗口1:窗口
{
公共窗口1()
{
MyString=“这是我的字符串”;
}
公共字符串MyString
{
得到;
设置
}
}
重要的部分是按钮上的标签(尽管您可以 轻松设置按钮的DataContext)。这将存储对的引用 父窗口。ContextMenu能够访问此 通过它的PlacementTarget属性。然后可以传递此上下文 浏览菜单项

我承认这不是世界上最优雅的解决方案。 然而,它胜过在代码背后设置东西。如果有人有 更好的方法我很想听听

万岁!这是:

绑定到WPF上下文菜单中的菜单项

2008年10月29日星期三-18

因为WPF中的上下文菜单不存在于 就页面/窗口/控件本身而言,数据绑定可能有点棘手。 我在网上到处寻找这个,而且最 常见的答案似乎是“只需在代码背后做”。错了!我 没有回到XAML的精彩世界 在代码后面做事情

下面是我的示例,它将允许您绑定到 作为窗口的属性存在

公共部分类窗口1:窗口
{
公共窗口1()
{
MyString=“这是我的字符串”;
}
公共字符串MyString
{
得到;
设置
}
}
重要的部分是按钮上的标签(尽管您可以 轻松设置按钮的DataContext)。这将存储对的引用 父窗口。ContextMenu能够访问此 通过它的PlacementTarget属性。然后可以传递此上下文 浏览菜单项

我承认这不是世界上最优雅的解决方案。 然而,它胜过在代码背后设置东西。如果有人有 更好的方法我很想听听


我发现它不适合我,因为菜单项是嵌套的,这意味着我必须遍历一个额外的“父项”才能找到PlacementTarget

更好的方法是将ContextMenu本身作为RelativeSource找到,然后绑定到它的放置目标。此外,由于标记是窗口本身,并且您的命令位于viewmodel中,因此还需要设置DataContext

我的结局是这样的

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

...
这意味着,如果你最终得到一个复杂的上下文菜单和子菜单等。。您不需要一直向每个级别命令添加“父级”

--编辑--

还提出了另一种方法,在绑定到窗口/用户控件的每个ListBoxItem上设置一个标记。我之所以这样做,是因为每个ListBoxItem都由它们自己的ViewModel表示,但我需要通过控件的顶级ViewModel执行菜单命令,但需要将它们作为参数传递给列表ViewModel

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

>
...

我发现它不适合我,因为菜单项被嵌套,这意味着我必须向上遍历一个额外的“父项”才能找到PlacementTarget

更好的方法
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));
}
<Grid.Resources>
    <local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</Grid.Resources>
<ContextMenu>
    <MenuItem Header="Test" Command="{Binding Source={StaticResource Proxy}, Path=Data.MyCommand}"/>
</ContextMenu>
<ContextMenu ContextMenuOpening="ContextMenu_ContextMenuOpening">
    <MenuItem Command="Save"/>
    <Separator></Separator>
    <MenuItem Command="Close"/>
    ...
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;
        } 
    }
}
<MenuItem Command="{Binding YourCommand}" CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>