代码之家  ›  专栏  ›  技术社区  ›  Heinzi

WPF:将ContextMenu绑定到MVVM命令

  •  22
  • Heinzi  · 技术社区  · 14 年前

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

    我收到的错误信息是

    DataContext 不是选项,因为此问题发生在可视化树的最下面,其中DataContext已经包含要显示的实际数据。我已经试过了 {RelativeSource FindAncestor, ...} 相反,但这会产生类似的错误消息。

    6 回复  |  直到 13 年前
        1
  •  55
  •   Daniel    10 年前

    问题是ContextMenu不在可视化树中,所以您基本上必须告诉ContextMenu要使用哪个数据上下文。

    退房 this blogpost

    他创建了一个继承Freezable并声明数据依赖属性的类代理。

    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>
    
        2
  •  17
  •   N_A    12 年前

    万岁 web.archive.org ! 这里是 the missing blog post

    2008年10月29日,星期三

    您的页面/窗口/控件本身,数据绑定可能有点棘手。 我在网上到处寻找这个,而且 常见的答案似乎是只在代码后面做。错了!我 没有回到XAML的奇妙世界 在代码后面做事情。

    作为窗口的属性存在。

    public partial class Window1 : Window
    {
        public Window1()
        {
            MyString = "Here is my string";
        }
    
        public string MyString
        {
            get;
            set;
    
        }
    }
    
        <Button Content="Test Button" Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
            <Button.ContextMenu>
                <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" >
                    <MenuItem Header="{Binding MyString}"/>
                </ContextMenu>
            </Button.ContextMenu>
        </Button>
    

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

    我承认这不是世界上最优雅的解决办法。 然而,它比在代码后面设置东西要好。如果有人

        3
  •  10
  •   nrjohnstone    11 年前

    我发现它不适合我,因为菜单项是嵌套的,这意味着我必须遍历一个额外的“父”来找到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>
    

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

    --编辑--

    还提出了另一种方法,在绑定到Window/Usercontrol的每个ListBoxItem上设置一个标记。我之所以这样做是因为每个ListBoxItem都由它们自己的ViewModel表示,但是我需要通过控件的顶级ViewModel执行菜单命令,但是将它们作为参数传递给list 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>
    
        4
  •  6
  •   Community CDub    8 年前

    HCLs answer ,这是我最终使用的:

    <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>
    
        5
  •  6
  •   Community CDub    8 年前

    看到了吗 this 贾斯汀·泰勒的文章。

    更新
    遗憾的是,被引用的博客已经没有了。我试图用另一个答案来解释这一过程。可以找到 here

        6
  •  2
  •   Tom Makin    8 年前

    如果(像我一样)您不喜欢难看的复杂绑定表达式,这里有一个简单的代码隐藏解决方案。这种方法仍然允许您在XAML中保持干净的命令声明。

    XAML编号:

    <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;
            } 
        }
    }
    
        7
  •  1
  •   Psykojello    5 年前

    2020年答案:

    我把这个答案留给其他在google上搜索过这个问题的人,因为这是第一个出现的搜索结果。 这对我很有效,而且比其他建议的解决方案更简单:

    <MenuItem Command="{Binding YourCommand}" CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
    

    如下所述:

    https://wpf.2000things.com/2014/06/19/1097-getting-items-in-context-menu-to-correctly-use-command-binding/