WPF:将ContextMenu绑定到MVVM命令

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

以下作品:

     

但以下不起作用。

          

我得到的错误信息是

System.Windows.Data错误:4:找不到引用’ElementName = myWindow’的绑定源。 BindingExpression:路径= mycommand的; 的DataItem = NULL; target元素是’MenuItem’(Name =”); target属性是’Command’(类型’ICommand’)

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

问题是ContextMenu它不在可视化树中,所以你基本上必须告诉Context菜单关于使用哪个数据上下文。

查看这篇博客文章,其中包含一个非常好的Thomas Levesque解决方案。

他创建了一个inheritanceFreezable的类Proxy,并声明了一个Data依赖属性。

 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)); } 

然后它可以在XAML中声明(在可视化树中已知正确的DataContext的位置):

    

并在可视化树之外的上下文菜单中使用:

    

Hurray for web.archive.org ! 这是缺少的博客文章 :

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

2008年10月29日星期三 – jtango18

因为WPF中的ContextMenu不存在于页面/窗口/控件本身的可视树中,所以数据绑定可能有点棘手。 我已经在网上搜索了高低,最常见的答案似乎是“只是在后面的代码中执行”。 错误! 我没有进入XAML的精彩世界,回到后面的代码中做事。

这是我的示例,它允许您绑定到作为窗口属性存在的字符串。

 public partial class Window1 : Window { public Window1() { MyString = "Here is my string"; } public string MyString { get; set; } }  

重要的部分是按钮上的标签(尽管您可以轻松设置按钮的DataContext)。 这将存储对父窗口的引用。 ContextMenu能够通过它的PlacementTarget属性访问它。 然后,您可以通过菜单项向下传递此上下文。

我承认这不是世界上最优雅的解决方案。 但是,它胜过后面代码中的设置。 如果有人有更好的方法来做到这一点,我很乐意听到它。

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

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

我最终得到了这样的东西

  ...         

这意味着如果你最终得到一个带有子菜单等的复杂上下文菜单..你不需要继续为每个级别的命令添加“父”。

– 编辑 –

还提出了这个替代方法,在每个ListBoxItem上设置一个标记,绑定到Window / Usercontrol。 我最终这样做是因为每个ListBoxItem都由它们自己的ViewModel表示,但是我需要通过顶层ViewModel为控件执行菜单命令,但是将它们的列表ViewModel作为参数传递。

   >  ...    

请参阅Justin Taylor的这篇文章以获得解决方法。

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

根据HCL的答案 ,这是我最终使用的:

  ...         

如果(像我一样)你厌恶丑陋的复杂绑定表达式,这里有一个简单的代码隐藏解决方案来解决这个问题。 此方法仍允许您在XAML中保持干净的命令声明。

XAML:

     ... 

代码背后:

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