C# WPF:将上下文菜单绑定到MVVM命令
假设我有一个属性返回命令的窗口(事实上,它是一个UserControl,在ViewModel类中有一个命令,但是让我们尽可能简单地重现问题) 以下工作: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" /> <
<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}}}"/>