使用父级DataContext(WPF - 动态菜单命令绑定)

32

我在这个网站和谷歌上搜索了解决方案,但都不适用于我的情况。

我有一个UserControl的ViewModel命令。 用户控件具有绑定到ObservableCollection的ItemsControl。在ItemsControl.ItemTemplate的DataTemplate中,我有一个按钮,我想使用该命令。我无法绑定命令,因为在DataTemplate中,DataContext不是ViewModel,而是ObservableCollection的一个项目。

问题是:如果丢失父数据上下文,如何将按钮绑定到命令?

我认为这需要一个简单的解决方案,因为我认为这是一个常见的问题。

想象一下这种情况:

你有一个ListBox项,其中observableCollection是ItemsSource,因此你正在使用ListBox中的datatemplate来处理集合中的每个元素。好的,你想删除所选项目,并在每行中放置一个按钮以执行此操作。你如何做到这一点?

在MVP中,我可以在按钮的click事件中实现:

Button but = e.Source as Button;

if (but != null)
      Presenter.ActualNote = but.DataContext as Note;
简单来说,您需要将行(即所选项)的数据上下文发送给Presenter。但是,我该如何使用mvvm方式呢?因为我需要使用命令,但我无法将命令分配给按钮,因为按钮不知道ViewModel(其中命令存在)的存在。正如您所看到的,按钮必须存在于DataTemplate中,那么DataContext就不再是ViewModel了...这就是为什么我需要访问父级的DataContext以访问命令的原因。希望您能更好地理解我的问题。谢谢。
4个回答

91

使用以下绑定作为按钮命令:

{Binding DataContext.CommandName, 
         RelativeSource={RelativeSource FindAncestor, 
                         AncestorType={x:Type MyUserControl}}}

这将告诉它找到您的UserControl并使用其DataContext。


1
我在示例中尝试了一下,对我有效。听起来像是语法错误。你能复制并粘贴你的按钮XAML吗? - Eddie Deyo
哦,没有错误,VS标记了它但编译通过了。我试着用一些简单的东西来解决它。我在用户控件中放置了一个Tag元素,并希望将其打印在菜单项的标题中:<MenuItem Header="{Binding Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />。但是,Visual Studio说:“无法找到引用为'RelativeSource FindAncestor,AncestorType ='System.Windows.Controls.UserControl',AncestorLevel ='1''的绑定源。BindingExpression:Path = Tag; DataItem = null; target element is 'MenuItem'(Name =''); target property is 'Header'(type'Object')。 - Jesus Rodriguez
+1!太好了,这终于成功了,之前用ElementName的方法失败了。 - Sören
2
太棒了,运行得很顺利! 另外需要说明的是:如果你仍想保留按钮的数据上下文以便传递给命令绑定,请使用CommandParameter="{Binding Path=.}"这种方式可以使参数对象成为控件的上下文。 - Simon Mattes
它说我的用户控件不存在。 - Isuru Herath
显示剩余2条评论

5

如果你想要一个不太规范的、破坏MVVM结构的解决方案,可以在按钮上设置Tag="{Binding}",并处理Click事件。在事件处理程序中,调用ViewModel上的命令。


我正在寻找一个不破坏MVVM的好解决方案,但谢谢,这是一个解决方案 :P。 - Jesus Rodriguez
2
我是一个SL开发人员,所以打破MVVM是家常便饭 :) - geofftnz
话说,我在我的应用程序中使用了半MVVM模式,而且效果不错。 - geofftnz
最终,我使用了这个解决方案,但没有使用标签属性。只需单击事件并执行命令,非常不规范,但我需要继续。我会在另一天尝试另一个解决方案。 - Jesus Rodriguez

3

好的,那么修改您的数据项类,使其具有引用整个模型视图的属性怎么样?

如果您的ItemsSource类型为ObservableCollection<DataItem>,则将DataItem类型修改如下:

public class DataItem
{
    public BusinessObject Value { get; set; }

    private ModelView modelView;

    public ModelView ModelView
    {
        get
        {
            return modelView;
        }
    }

    public DataItem(ModelView modelView)
    {
        this.modelView = modelView;
    }
}

你能再解释一遍吗?用我的英语,我无法理解你试图向我解释什么。抱歉。 - Jesus Rodriguez
添加了更详细的解释。 - Dmitry Tashkinov

2

RelativeSource可以使用,但我认为让控件在彼此的属性之间游荡并不正确。将按钮放置在项视图内部,它会对外部数据源执行操作而不是绑定的项,这很奇怪。您可能需要审查程序代码的设计。


我喜欢你的答案,但是我需要它用于这个解决方案。我有一个itemscontrol,里面有一个网格,每个项目都是一个注释。好吧,我在网格中有一个上下文菜单,其中包含有关“选定”注释的一些选项。每个选项都是一个命令,但是菜单的数据上下文是实际的注释而不是视图模型。我不能将菜单放在外面,因为我需要为每个项目提供菜单。 - Jesus Rodriguez
嗯,是的,我不认为这是一个理想的解决方案,但您可以将ModelView对象作为静态资源放置在Window.Resources元素中,也许使用ObjectDataProvider,并通过Static扩展在窗口的任何部分从任何数据绑定中引用它。 - Dmitry Tashkinov
我同意 - 每当我使用这个解决方案时,它总是感觉像是一个hack -- 但是一个可行的hack。 ;) - Eddie Deyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接