Using MVVM Light EventToCommand In DataTemplate - wpf

So I have a WPF UserControl:
<UserControl x:Class="BI_Builder.Views.ObjectTreeView"
x:Name="UC1"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:BI_Builder"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:viewModels="clr-namespace:BI_Builder.ViewModels"
xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" DataContext="{Binding}">
<UserControl.Resources>
<ContentControl x:Key="Context" Content="{Binding}" />
<DataTemplate x:Key="DataSourceTemplate">
<TextBlock Text="{Binding Path=Name}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<command:EventToCommand Command="{Binding Path=DataContext.OpenCommand, Mode=OneWay,ElementName=UC1}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ItemTemplate"
ItemsSource="{Binding Children}"
ItemTemplate="{StaticResource DataSourceTemplate}">
<StackPanel>
<TextBlock Text="{Binding Header}">
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</UserControl.Resources>
<Grid>
<TreeView Name="TreeView" ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ItemTemplate}" >
</TreeView>
</Grid>
</UserControl>
And here's the main view model for the user control:
public class ObjectTreeViewModel : ObservableObject {
public ObservableCollection<ItemViewModel> Items {
get {
if (_items != null) return _items;
_items = new ObservableCollection<ItemViewModel>();
_items.Add(DataSources);
return _items;
}
set { _items = value;
}
}
public ItemViewModel DataSources {
get { return _dataSources ?? (_dataSources = new ItemViewModel() { Header = "Data Sources", Children = new ObservableCollection<object>(DataSourceList) }); }
set { _dataSources = value; }
}
public List<DataSource> DataSourceList;
public ICommand OpenCommand {
get { if (_openCommand == null) { return _openCommand = new RelayCommand(OpenDataSource); } return _openCommand; }
}
private void OpenDataSource() {
MessageBox.Show("Test");
}
public ObjectTreeViewModel() {
DataSourceList = new List<DataSource>();
DataSourceList.Add(new DataSource() { Name = "Test" });
}
private ItemViewModel _dataSources;
private ObservableCollection<ItemViewModel> _items;
private RelayCommand _openCommand;
}
}
I've tried every method I've come across on the web to get the EventToCommand in the DataSourceTemplate DataTemplate to fire. In fact, I'm pretty sure it knows where the OpenCommand is, because if I change the Path to gobbledygook, the Output window throws me an error saying that "ObjectTreeView" (which is the instance of the ObjectTreeViewModel view model being bound to the UserControl) doesn't have the gobbledygook property. So I think I've set the DataContext correctly ...
But whenever I click on the text blocks ... nothing.
Really trying to avoid code-behind (it just feels wrong), and full disclosure, I'm using MVVM Light's EventToCommand but not the full toolkit, although I'm tempted to rewrite what I have so far in it to see if using the Service Locator will solve this problem.

The TextBlock control does not have a Click event. See MSDN.
You should use the MouseLeftButtonDown event instead:
<i:EventTrigger EventName="MouseLeftButtonDown">
<!-- ... -->
</i:EventTrigger>

Can you put a hyperlink inside your textblock instead and bind the command to the hyperlink?
Note you can style the hyperlink to look like a plain textblock if needed.
<TextBlock>
<Hyperlink Command="{Binding Path=DataContext.OpenCommand" Text="{Binding Path=Name}" />
</TextBlock>
Also make sure that the ObjectTreeView class is instantiated and loaded into DataContext of the usercontrol.

Related

binding to an property of built-in control within custom usercontrol from another usercontrol

Due to the origin MainWindow.xaml was too large, really difficult to maintain. so I separate them into several User Controls. However I encountered the following issue, one control in UserControl_2 is refer to ListView's selection within UserControl_1. I tried to alter the binding, but none of them working as expected. Any idea how to binding to another User Control correctly?
MainWindow.xaml:
<Window x:Class="MyApp.MainWindow" ...>
<Grid>
<view:UserControl_1/>
<view:UserControl_2/>
</Grid>
</Window>
UserControl_1.xaml:
<UserControl x:Class="MyApp.views.UserControl_1 ...>
<Grid>
<ListView x:Name="MyListView" />
</Grid>
</UserControl>
UserControl_2.xaml
<UserControl x:Class="MyApp.views.UserControl_2 ...>
<Grid>
<Button Content="Test"
Command="TestCommand"
CommandParameter="{Binding Path=MyListView.SelectedIndex,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl_1}}}"
</Grid>
</UserControl>
Create a view model class and set this one as the DataContext of the parent window:
public class ViewModel
{
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml:
<Window x:Class="MyApp.MainWindow" ...>
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<view:UserControl_1/>
<view:UserControl_2/>
</Grid>
</Window>
You can then bind the ListView and the Button in the user controls to the same source property.
UserControl_1.xaml:
<UserControl x:Class="MyApp.views.UserControl_1 ...>
<Grid>
<ListView x:Name="MyListView" SelectedIndex="{Binding DataContext.SelectedIndex, RelativeSource={RelativeSource AncestorType=Window}}" />
</Grid>
</UserControl>
UserControl_2.xaml:
<UserControl x:Class="MyApp.views.UserControl_2 ...>
<Grid>
<Button Content="Test"
Command="TestCommand"
CommandParameter="{Binding Path=DataContext.SelectedIndex,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</Grid>
</UserControl>

prism 5.0 InvokeCommandAction SelectionMode="Multiple"

I want to detect all selected items in List Box through InvokeCommandAction prism 5.0.
XAML:
<Window x:Class="Selection.Prism5._0.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:Selection.Prism5"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<FrameworkElement.DataContext>
<local:MainViewModel />
</FrameworkElement.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectItemsCommand}"
TriggerParameterPath="AddedItems" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
View Model:
public class MainViewModel : BindableBase
{
public MainViewModel()
{
this.Items = new List<Model>
{
new Model {Id=1,Name="Name 1" },
new Model {Id=2,Name="Name 2" },
new Model {Id=3,Name="Name 3" },
new Model {Id=4,Name="Name 4" },
new Model {Id=5,Name="Name 5" },
new Model {Id=6,Name="Name 6" }
};
SelectItemsCommand = new DelegateCommand<object[]>((items) =>
{
if (items != null && items.Count() > 0)
{
SelectedItems = items.Select(i => (Model)i);
}
});
}
public ICommand SelectItemsCommand { get; private set; }
private IEnumerable<Model> _items;
public IEnumerable<Model> Items
{
get { return _items; }
set
{
_items = value; base.OnPropertyChanged("Items");
}
}
private IEnumerable<Model> _selectedItems;
public IEnumerable<Model> SelectedItems
{
get { return _selectedItems; }
set
{
_selectedItems = value; base.OnPropertyChanged("SelectedItems");
}
}
private Model _selectedItem;
public Model SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value; base.OnPropertyChanged("SelectedItem");
}
}
}
It should work, however it doesn't. When I select more than one item in GUI:
View model code recognizes only one item:
I saw this example in "What's New in Prism 5.0" by Brian Lagunas and as far as I understood, this technic may be used for multi-selection too.
What have I done wrong here?
SelectionChangedEventArgs.AddedItems gives you the selection list of a particular action. if ListBox SelectionMode is "Multiple" or "Single" you cannot select multiple items at a single click. if ListBox SelectionMode is "Extended" you can select multiple items with the help of the shift key.
SelectionChangedEventArgs.AddedItems will not give all selected items of the list box for a particular action.
For your need,
In View,
Change TriggerParameterPath="AddedItems" to TriggerParameterPath="Source.SelectedItems" .
In ViewModel
Change DelegateCommand<object[]> to DelegateCommand<System.Collections.ICollection>
Ref:
http://frststart.blogspot.com/2016/10/selectionchangedselecteditemstutorial.html
You are selecting one item at a time and the command is invoked for each time you select an item.
This is the expected behaviour.
If you want to keep track of the currently selected items you could add an IsSelected property to the Model class and use an ItemContainerStyle to bind this one to the IsSelected property of the ListBoxItem container:
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectItemsCommand}"
TriggerParameterPath="AddedItems" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectItemsCommand = new DelegateCommand<System.Collections.IList>((items) =>
{
SelectedItems = Items.Where(x => x.IsSelected).ToList();
});

ContextMenu on TreeView-Element in MVVM-style

I have the "simple" task to have a ContextMenu on a TreeView(Element) that is done in MVVM-way.
When searching the web I found some solutions that I could bring to work with buttons etc. but not with the TreeView. I think the problem is with setting the ItemsSource-Property of TreeView that gives every single item an own DataContext.
Here's my little Test-App where you can see the principle working for button but not for the TreeView-Elements:
MainWindow.xaml:
<Window x:Class="ContextMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid >
<StackPanel>
<TextBlock Text="{Binding MyText}" />
<Button Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=Self}}" Content="Click me">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding PlacementTarget.Tag.MyText,
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
<TreeView ItemsSource="{Binding MyList}" Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=Self}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag.MyText,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Grid>
</Window>
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowVM();
}
}
MainWindowVM.cs:
public class MainWindowVM
{
public string MyText { get; set; }
public ObservableCollection<TreeElement> MyList { get; set; }
public MainWindowVM()
{
MyText = "This is my Text!";
MyList = new ObservableCollection<TreeElement>();
MyList.Add(new TreeElement("String 1"));
MyList.Add(new TreeElement("String 2"));
}
}
public class TreeElement
{
public string Name { get; set; }
public TreeElement(string Name)
{
this.Name = Name;
}
}
Thanks for your help!!
Joerg
You are close.
What you are doing:
you set the Tag of TreeView to its own DataContext. This is
unnecessary.
you try to get Tag.MyText from your ContextMenu.PlacementTarget - which is TextBlock. The TextBlock has no Tag set.
What you should do:
set the Tag of the TextBlock to DataContext of the Window
(Window is TextBlock ancestor and you should look it up via
RelativeSource Mode=FindAncestor).
the second part is OK - you have TextBlock.Tag set in the first step.

Silverlight MVVM - How Can I Declaratively Bind a ComboBox in a DataGrid CellEditingTemplate using a ViewModel?

I am trying to declaratively bind a ComboBox within a DataGrid CellEditingTemplate using a ViewModel. The ComboBox is not being bound. What am I doing wrong?
XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:data="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
x:Class="SilverlightApplication1.EmployeeDetail"
Width="640" Height="480">
<UserControl.Resources>
<data:EmployeeDetailsViewModel
x:Key="ViewModel"
d:IsDataSource="True" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource ViewModel}}" Background="White">
<sdk:DataGrid ItemsSource="{Binding Employees,Mode=TwoWay}" AutoGenerateColumns="False" CanUserSortColumns="True" CanUserReorderColumns="True" CanUserResizeColumns="True" GridLinesVisibility="All" Height="317" HorizontalAlignment="Left" Margin="12,136,0,0" Name="EmployeesGrid" VerticalAlignment="Top" Width="605">
<sdk:DataGrid.Columns>
<!-- snipped from brevity -->
<sdk:DataGridTemplateColumn Header="Status">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding EmployeeStatus.Description}" TextWrapping="Wrap"></TextBlock>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=EmployeeStatuses}" SelectedItem="{Binding EmployeeStatus, Mode=TwoWay}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<TextBlock x:Name="SearchLabel" HorizontalAlignment="Left" Margin="12,95,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="106" Height="34"><Run FontWeight="Bold" Text="Search By Name: "/><Run FontSize="9.333" Text="(Last, First)"/></TextBlock>
<TextBox x:Name="SearchParam" HorizontalAlignment="Left" Margin="144,101,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="162"/>
<Button x:Name="SearchButton" Content="Search" HorizontalAlignment="Right" Margin="0,102,242,0" VerticalAlignment="Top" Width="75" Click="SearchButton_Click"/>
</Grid>
</UserControl>
VIEW MODEL:
using System.Collections.ObjectModel;
using SilverlightApplication1.EmployeeService;
using SilverlightApplication1.ViewModels;
namespace SilverlightApplication1
{
public class EmployeeDetailsViewModel : ViewModelBase
{
readonly IEmployeeServiceAgent _serviceAgent;
ObservableCollection<EmployeeStatus> _employeeStatuses { get; set; }
ObservableCollection<Employee> _employees { get; set; }
public EmployeeDetailsViewModel() : this(new EmployeeServiceAgent()) { }
public EmployeeDetailsViewModel(IEmployeeServiceAgent serviceAgent)
{
if (!IsDesignTime)
{
_serviceAgent = serviceAgent;
GetAllEmployees();
GetEmployeeStatuses();
}
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set
{
if(_employees!=value)
{
_employees = value;
OnNotifyPropertyChanged("Employees");
}
}
}
public ObservableCollection<EmployeeStatus> EmployeeStatuses
{
get { return _employeeStatuses; }
set
{
if (_employeeStatuses != value)
{
_employeeStatuses = value;
OnNotifyPropertyChanged("EmployeeStatuses");
}
}
}
private void GetAllEmployees()
{
_serviceAgent.GetAll((s, e) => Employees = e.Result);
}
private void GetEmployeeStatuses()
{
_serviceAgent.GetEmployeeStatuses((s, e) => EmployeeStatuses = e.Result);
}
}
}
Update:
This seems wrong but I figured out how to get the binding working by re-referencing the ViewModel in the ItemSource Binding:
<ComboBox ItemsSource="{Binding Source={StaticResource ViewModel},Path=EmployeeStatuses}"
DisplayMemberPath="Description"
SelectedItem="{Binding EmployeeStatus, Mode=TwoWay}" />
However, a am now experiencing a problem where the SelectedItem is not bound! What am I doing wrong?
The problem is a common one that people run into. When you're in the data template of the column, you're no longer bound the the view model. At that point your data context is the EmployeeStatus object (which doesn't have an EmployeeStatuses property to bind to).
So to get the combobox binding to work you can use the ElementName=LayoutRoot to bind back up the tree to the root ViewModel.
Update: Here would be the full syntax for your binding:
{Binding DataContext.EmployeeStatuses, ElementName=LayoutRoot}
Update2: I've actually run into this as well and there is a workaround you have to implement to get the element name binding to work inside a datagrid.
If Bryant's solution does not work (in SL4), use static resources. See this link: http://blog.digitaltools.com/post/2011/05/06/Binding-a-Datagride28099s-ComboBox.aspx
Or, by creating the static resource in xaml: http://forums.silverlight.net/post/370135.aspx

WPF Context menu doesn't bind to right databound item

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:
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:
<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:
<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:
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:
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:
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:
public class Person
{
public string Name { get; set; }
}
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.
<MenuItem Header="Change" Command="{Binding
Path=PlacementTarget.ChangeCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
/>
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.
The XAML might look like this:
<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.
Note: this XAML was copied from a working project and simplified for illustration. There may be typos or other minor errors.
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.
I posted a fully working sample on Github. Here is an excerpt of the 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>
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
}
I found this method using the Tag property very useful when binding from a context menu deep inside a control template:
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.
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.
This is the solution I found to make it work only using 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>

Resources