wpf MVVM 绑定命令到上下文菜单项

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9994241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 23:21:34  来源:igfitidea点击:

MVVM binding command to contextmenu item

wpfmvvm

提问by Valyrion

I'm trying to bind a command to a menuitem in WPF. I'm using the same method that's been working for all my other command bindings, but I can't figure out why it doesn't work here.

我正在尝试将命令绑定到 WPF 中的菜单项。我正在使用与所有其他命令绑定相同的方法,但我不知道为什么它在这里不起作用。

I'm currently binding my commands like this:

我目前正在像这样绑定我的命令:

Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.MyCommand}"

This is where it goes wrong (this is inside a UserControl)

这是出错的地方(这是在 UserControl 内)

<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}" 
                        Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">

     <Button.ContextMenu>
         <ContextMenu>
             <MenuItem Header="Remove" CommandParameter="{Binding Name}"
                                      Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.RemoveCommand}"/>
         </ContextMenu>
     </Button.ContextMenu>
     ...

The first command binding works like it should, but the second one refuses to do anything. I've tried changing the ancestor level and naming my Control to access it through ElementName instead of RelativeSource, but still no change. It keeps saying "Cannot find source for binding with reference..."

第一个命令绑定像它应该的那样工作,但第二个命令拒绝做任何事情。我尝试更改祖先级别并命名我的控件以通过 ElementName 而不是 RelativeSource 来访问它,但仍然没有变化。它一直说“找不到与引用绑定的源...”

What am I missing?

我错过了什么?

回答by MetalMikester

(Edit) Since you mentioned this is in an ItemsControl's template, things are different:

(编辑)由于您提到这是在 ItemsControl 的模板中,因此情况有所不同:

1) Get the BindingProxy class from this blog (and read the blog, as this is interesting information): How to bind to data when the DataContext is not inherited.

1) 从这个博客获取 BindingProxy 类(并阅读博客,因为这是有趣的信息):如何在未继承 DataContext 时绑定到数据

Basically the elements in the ItemsControl (or ContextMenu) are not part of the visual or logical tree, and therefore cannot find the DataContext of your UserControl. My apologies for not writing more on this here, but the author has done a good job explaining it step by step, so there's no way I could give a complete explanation in just a few lines.

基本上 ItemsControl(或 ContextMenu)中的元素不是可视化或逻辑树的一部分,因此无法找到 UserControl 的 DataContext。我很抱歉没有在这里写更多,但是作者已经很好地一步一步地解释了它,所以我不可能只用几行就给出完整的解释。

2) Do something like this: (you may have to adapt it a bit to make it work in your control):

2)做这样的事情:(您可能需要稍微调整一下以使其在您的控制下工作):

a. This will give you access to the UserControl DataContext using a StaticResource:

一种。这将使您可以使用 StaticResource 访问 UserControl DataContext:

<UserControl.Resources>
<BindingProxy
  x:Key="DataContextProxy"
  Data="{Binding}" />
</UserControl.Resources>

b. This uses the DataContextProxy defined in (a):

湾 这使用了 (a) 中定义的 DataContextProxy:

<Button.ContextMenu>
 <ContextMenu>
     <MenuItem Header="Remove" CommandParameter="{Binding Name}"
         Command="{Binding Path=Data.RemoveCommand, Source={StaticResource DataContextProxy}}"/>
 </ContextMenu>

This has worked for us in things like trees and datagrids.

这在树和数据网格等方面对我们有用。

回答by koshdim

ContextMenu is in different logical tree, that's why RelativeSource doesnt work. But context menu inherit DataContext from its "container", in this case it is Button. It is enough in common case but in your case you need two "data contexts", of ItemsControl item and of ItemsControl itself. I think you have no other choice but combine your view models into one, implement custom class to be used as ItemsControl item data context and contain both "Name" and "Remove command" or your item's view model can define RemoveCommand "proxy", that would call parent command internally

ContextMenu 位于不同的逻辑树中,这就是 RelativeSource 不起作用的原因。但是上下文菜单从它的“容器”继承了 DataContext,在这种情况下它是 Button。在常见情况下就足够了,但在您的情况下,您需要两个“数据上下文”,即 ItemsControl 项和 ItemsControl 本身。我认为您别无选择,只能将您的视图模型合并为一个,实现用作 ItemsControl 项目数据上下文的自定义类并包含“名称”和“删除命令”,或者您项目的视图模型可以定义 RemoveCommand“代理”,即会在内部调用父命令

EDIT: I slightly changed Baboon's code, it must work this way:

编辑:我稍微改变了狒狒的代码,它必须这样工作:

<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}" 
    Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
    Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">
            <Button.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Remove" 
                   CommandParameter="{Binding Name}"
                   Command="{Binding Path=PlacementTarget.Tag.DataContext.RemoveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
                </ContextMenu>
            </Button.ContextMenu>

回答by Tarik.J

koshdim is spot on, it works like a charm!! Thanks Koshdim

koshdim 恰到好处,它就像一个魅力!谢谢科什迪姆

I modified his code to fit in my context menu

我修改了他的代码以适应我的上下文菜单

    <DataGrid 
        AutoGenerateColumns="False" 
        HeadersVisibility="Column"
        Name="dgLosses"
        SelectedItem="{Binding SelectedItem, Mode= TwoWay}"
        AllowDrop="True"
        ItemsSource="{Binding Losses}"
        Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}">


        <DataGrid.ContextMenu >
            <ContextMenu >
                <MenuItem Header="Move to Top     "   Command="{Binding PlacementTarget.Tag.MoveToTopCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
                <MenuItem Header="Move to Period 1"   Command="{Binding PlacementTarget.Tag.MoveToPeriod1Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
                <MenuItem Header="Move to Period 2"   Command="{Binding PlacementTarget.Tag.MoveToPeriod2Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
                <MenuItem Header="Move to Period 3"   Command="{Binding PlacementTarget.Tag.MoveToPeriod3Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>                    
            </ContextMenu>
        </DataGrid.ContextMenu>

回答by Louis Kottmann

That's a tricky issue, sure marginally you will find a quick workaround, but here is a no-magic-solution:

这是一个棘手的问题,你肯定会找到一个快速的解决方法,但这里有一个没有魔法的解决方案:

<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}" 
        Tag={Binding}
        Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">    
     <Button.ContextMenu>
         <ContextMenu>
             <MenuItem Header="Remove" 
                       CommandParameter="{Binding Path=PlacementTarget.Tag.Name, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
                       Command="{Binding Path=PlacementTarget.Tag.RemoveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
         </ContextMenu>
     </Button.ContextMenu>
...

It boils down to using the Tagof the PlacementTarget(the Buttonhere).

它归结为使用TagPlacementTarget(在Button这里)。