WPF 上下文菜单未绑定到正确的数据绑定项
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/662164/
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
WPF Context menu doesn't bind to right databound item
提问by Cameron MacFarland
I have a problem when binding a command in a context menu on a usercontrol that is on a tab page. The first time I use the menu (right-click on the tab) it works great, but if I switch tab the command will use the databound instance that was used the first time.
在选项卡页上的用户控件的上下文菜单中绑定命令时遇到问题。我第一次使用菜单(右键单击选项卡)时效果很好,但是如果我切换选项卡,命令将使用第一次使用的数据绑定实例。
If I put a button that is bound to the command in the usercontrol it works as expected...
如果我在用户控件中放置一个绑定到命令的按钮,它会按预期工作......
Can someone please tell me what I'm doing wrong??
有人可以告诉我我做错了什么吗??
This is a test project that exposes the problem:
这是一个暴露问题的测试项目:
App.xaml.cs:
应用程序.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
CompanyViewModel model = new CompanyViewModel();
Window1 window = new Window1();
window.DataContext = model;
window.Show();
}
}
Window1.xaml:
Window1.xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="HeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type vw:PersonViewModel}">
<vw:UserControl1/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Path=Persons}"
ItemTemplate="{StaticResource HeaderTemplate}"
IsSynchronizedWithCurrentItem="True" />
</Grid>
</Window>
UserControl1.xaml:
用户控件1.xaml:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinWidth="200">
<UserControl.ContextMenu>
<ContextMenu >
<MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/>
</ContextMenu>
</UserControl.ContextMenu>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0">The name:</Label>
<TextBox Grid.Column="1" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
CompanyViewModel.cs:
公司视图模型.cs:
public class CompanyViewModel
{
public ObservableCollection<PersonViewModel> Persons { get; set; }
public CompanyViewModel()
{
Persons = new ObservableCollection<PersonViewModel>();
Persons.Add(new PersonViewModel(new Person { Name = "Kalle" }));
Persons.Add(new PersonViewModel(new Person { Name = "Nisse" }));
Persons.Add(new PersonViewModel(new Person { Name = "Jocke" }));
}
}
PersonViewModel.cs:
PersonViewModel.cs:
public class PersonViewModel : INotifyPropertyChanged
{
Person _person;
TestCommand _testCommand;
public PersonViewModel(Person person)
{
_person = person;
_testCommand = new TestCommand(this);
}
public ICommand ChangeCommand
{
get
{
return _testCommand;
}
}
public string Name
{
get
{
return _person.Name;
}
set
{
if (value == _person.Name)
return;
_person.Name = value;
OnPropertyChanged("Name");
}
}
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
TestCommand.cs:
测试命令.cs:
public class TestCommand : ICommand
{
PersonViewModel _person;
public event EventHandler CanExecuteChanged;
public TestCommand(PersonViewModel person)
{
_person = person;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_person.Name = "Changed by command";
}
}
Person.cs:
人物.cs:
public class Person
{
public string Name { get; set; }
}
回答by Cameron MacFarland
The key thing to remember here is context menus are not part of the visual tree.
这里要记住的关键是上下文菜单不是可视化树的一部分。
Therefore they don't inherit the same source as the control they belong to for binding. The way to deal with this is to bind to the placement target of the ContextMenu itself.
因此,它们不会继承与它们所属的用于绑定的控件相同的源。处理这种情况的方法是绑定到 ContextMenu 本身的放置目标。
<MenuItem Header="Change" Command="{Binding
Path=PlacementTarget.ChangeCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
/>
回答by CyberMonk
The cleanest way I have found to bind commands to context menu items involves using a class called CommandReference. You can find it in the MVVM toolkit on Codeplex at WPF Futures.
我发现将命令绑定到上下文菜单项的最简洁方法是使用名为 CommandReference 的类。您可以在WPF Futures 的Codeplex 上的 MVVM 工具包中找到它。
The XAML might look like this:
XAML 可能如下所示:
<UserControl x:Class="View.MyView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ViewModel;assembly=MyViewModel"
xmlns:mvvm="clr-namespace:ViewModelHelper;assembly=ViewModelHelper"
<UserControl.Resources>
<mvvm:CommandReference x:Key="MyCustomCommandReference" Command="{Binding MyCustomCommand}" />
<ContextMenu x:Key="ItemContextMenu">
<MenuItem Header="Plate">
<MenuItem Header="Inspect Now" Command="{StaticResource MyCustomCommandReference}"
CommandParameter="{Binding}">
</MenuItem>
</MenuItem>
</ContextMenu>
</UserControl.Resources>
MyCustomCommand is a RelayCommand on the ViewModel. In this example, the ViewModel was attached to the view's datacontext in the code-behind.
MyCustomCommand 是 ViewModel 上的 RelayCommand。在此示例中,ViewModel 附加到代码隐藏中的视图数据上下文。
Note: this XAML was copied from a working project and simplified for illustration. There may be typos or other minor errors.
注意:此 XAML 是从一个工作项目中复制的,并且为了说明进行了简化。可能有错别字或其他小错误。
回答by Manuel Laflamme
I had the same issue recently with a ContextMenu located in a ListBox. I tried to bind a command the MVVM way without any code-behind. I finally gave up and I asked a friend for his help. He found a slightly twisted but concise solution. He is passing the ListBox in the DataContext of the ContextMenu and then find the command in the view model by accessing the DataContext of the ListBox. This is the simplest solution that I have seen so far. No custom code, no Tag, just pure XAML and MVVM.
我最近在 ListBox 中的 ContextMenu 遇到了同样的问题。我试图在没有任何代码隐藏的情况下以 MVVM 方式绑定命令。我终于放弃了,我向一位朋友寻求帮助。他找到了一个稍微扭曲但简洁的解决方案。他是在ContextMenu的DataContext中传递ListBox,然后通过访问ListBox的DataContext在视图模型中找到命令。这是迄今为止我见过的最简单的解决方案。没有自定义代码,没有标签,只有纯 XAML 和 MVVM。
I posted a fully working sample on Github. Here is an excerpt of the XAML.
我在Github上发布了一个完整的示例。这是 XAML 的摘录。
<Window x:Class="WpfListContextMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow" Height="350" Width="268">
<Grid>
<DockPanel>
<ListBox x:Name="listBox" DockPanel.Dock="Top" ItemsSource="{Binding Items}" DisplayMemberPath="Name"
SelectionMode="Extended">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show Selected" Command="{Binding Path=DataContext.ShowSelectedCommand}"
CommandParameter="{Binding Path=SelectedItems}" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</DockPanel>
</Grid>
</Window>
回答by Tim Valentine
回答by Ahmed Yasin Ko?ulu
I prefer another solution. Add context menu loader event.
我更喜欢另一种解决方案。添加上下文菜单加载器事件。
<ContextMenu Loaded="ContextMenu_Loaded">
<MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/>
</ContextMenu>
Assign data context within the event.
在事件中分配数据上下文。
private void ContextMenu_Loaded(object sender, RoutedEventArgs e)
{
(sender as ContextMenu).DataContext = this; //assignment can be replaced with desired data context
}
回答by rodrigogq
I know this is already an old post, but I would like to add another solution for those one who are looking for different ways to do it.
我知道这已经是一篇旧帖子了,但我想为那些正在寻找不同方法的人添加另一种解决方案。
I could not make the same solution to work in my case, since I was trying to do something else: open the context menu with a mouse click (just like a toolbar with a submenu attached to it) and also bind commands to my model. Since I was using an Event Trigger, the PlacementTarget object was null.
在我的情况下,我无法使用相同的解决方案,因为我试图做其他事情:单击鼠标打开上下文菜单(就像带有子菜单的工具栏一样),并将命令绑定到我的模型。由于我使用的是事件触发器,因此 PlacementTarget 对象为空。
This is the solution I found to make it work only using XAML:
这是我发现仅使用 XAML 使其工作的解决方案:
<!-- This is an example with a button, but could be other control -->
<Button>
<...>
<!-- This opens the context menu and binds the data context to it -->
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.DataContext">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{Binding}"/>
</ObjectAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
<!-- Here it goes the context menu -->
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Item 1" Command="{Binding MyCommand1}"/>
<MenuItem Header="Item 2" Command="{Binding MyCommand2}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
回答by Christian Myksvoll
I found this method using the Tag property very useful when binding from a context menu deep inside a control template:
我发现这种使用 Tag 属性的方法在从控件模板深处的上下文菜单中进行绑定时非常有用:
http://blog.jtango.net/binding-to-a-menuitem-in-a-wpf-context-menu
http://blog.jtango.net/binding-to-a-menuitem-in-a-wpf-context-menu
This makes it possible to bind to any datacontext available to the control that the context menu was opened from. The context menu can access the clicked control through "PlacementTarget". If the Tag property of the clicked control is bound to a desired datacontext, binding to "PlacementTarget.Tag" from inside the context menu will slingshot you directly to that datacontext.
这使得可以绑定到可用于打开上下文菜单的控件的任何数据上下文。上下文菜单可以通过“PlacementTarget”访问被点击的控件。如果单击的控件的 Tag 属性绑定到所需的数据上下文,则从上下文菜单中绑定到“PlacementTarget.Tag”会将您直接弹到该数据上下文。