ContextMenu on ListBox Item with DataTemplate - wpf

I have a ListBox with different classes of items. DataTemplates are used to present those objects in the appropriate way. I want to have different context menus in the DataTemplates of these classes.
Everything works fine using the mouse, but using the keyboard I can't bring up the context menu.
This is probably because the keyboard-focus is not on the contents of the DataTemplate, but on the ListBoxItem.
How can I get the ListBoxItem to refer to the Content's ContextMenu?
Sample code:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:WpfApplication8"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type my:Orange}">
<TextBlock>
Orange
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Peel"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type my:Apple}">
<TextBlock>
Apple
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Uncore"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Fruits}"/>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
namespace WpfApplication8
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Fruits = new ObservableCollection<Fruit>();
Fruits.Add(new Apple());
Fruits.Add(new Apple());
Fruits.Add(new Orange());
this.DataContext = this;
}
public ObservableCollection<Fruit> Fruits { get; set; }
}
public class Fruit
{
}
public class Apple : Fruit
{
}
public class Orange : Fruit
{
}
}

I too had this problem. Reading Bea Stollnitz' blog gave me an idea.
I started with a data template like this in my resources:
<ContextMenu x:Key="MyMenu">
<MenuItem Header="A" />
<MenuItem Header="B" />
<MenuItem Header="C" />
</ContextMenu>
<DataTemplate x:Key="MyTemplateKey" DataType="{x:Type local:myType}">
<TextBlock ContextMenu="{StaticResource MyMenu}" >
<Run Text="{Binding Path=MyBindingPath}" FontSize="20" FontWeight="Bold" />
</TextBlock>
</DataTemplate>
As described above, this causes the keyboard menu key not to invoke the context menu, although right clicking does work. The problem is the context menu needs to be on the ListBoxItem, not the template inside.
Hey presto!
<Style x:Key="ContextLBI" TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyMenu}">
</Setter>
</Style>
Now, just remove the ContextMenu from the data template, and set your style on your list box like this:
<ListBox ItemTemplate="{StaticResource MyTemplateKey}"
ItemContainerStyle="{StaticResource ContextLBI}"
... >
</ListBox>

This guy have similar problem as you: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5737a331-2014-4e39-b87c-215ae6a7cdd4.
Instead of fighting with focus, add a context menu to the listbox. Add a ContextMenuOpening event handler to your listbox. In that handler, depending on data context of currently selected item, add whatever menuitems you need programmatically.

I found a solution. In the code-behind I will give each ListBoxItem the context menu I find from its visual children.
It gives me the possibility of adding the context menus to the DataTemplates for the various class, thus giving me the polymorphism I like. I also prefer to declare the menus in XAML. And it works with keyboard navigation, as well as mouse use.
The code could probably have been put in an attached property or something for elegance.
I add a loaded event handler and this code:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
foreach (var item in list.Items)
{
ListBoxItem lbItem = list.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
lbItem.ContextMenu = FindContextMenu(lbItem);
}
}
private ContextMenu FindContextMenu(DependencyObject depObj)
{
ContextMenu cm = depObj.GetValue(ContextMenuProperty) as ContextMenu;
if (cm != null)
return cm;
int children = VisualTreeHelper.GetChildrenCount(depObj);
for (int i = 0; i < children; i++)
{
cm = FindContextMenu(VisualTreeHelper.GetChild(depObj, i));
if(cm != null)
return cm;
}
return null;
}

Related

Load controls on runtime based on selection

I'm new to XAML and I have a case where I need to change controls based on a selection on a combobox with templates.
For example, let's say that a user selects a template that requires a day of week and a time range that something will be available. I would like that, on the moment of the selection, the control with the information needed get build on the screen and that the bindings get to work as well.
Can someone give me a hint or indicate an article with an elegant way to do so?
Thanks in advance.
The solution you are looking for is a ContentControl and DataTemplates. You use the selected item of the ComboBox to change ContentTemplate of the Content Control.
You question mentions binding so I will assume you understand the MVVM pattern.
As an example, lets use MyModel1 as the Model
public class MyModel1
{
private Collection<string> values;
public Collection<string> Values { get { return values ?? (values = new Collection<string> { "One", "Two" }); } }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
And MyViewModel as the ViewModel
public class MyViewModel
{
public MyViewModel()
{
Model = new MyModel1();
}
public MyModel1 Model { get; set; }
}
And the code behind does nothing but instantiate the ViewModel.
public partial class MainWindow : Window
{
public MainWindow()
{
ViewModel = new MyViewModel();
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
All three are very simple classes. The fun comes in the Xaml which is
<Window x:Class="StackOverflow._20893945.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:this="clr-namespace:StackOverflow._20893945"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="MyModel1Template1" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 1"></TextBlock>
<ComboBox ItemsSource="{Binding Path=Values}" SelectedItem="{Binding Path=Field1}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MyModel1Template2" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 2"></TextBlock>
<TextBox Text="{Binding Path=Field2}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="2">
<ComboBox x:Name="TypeSelector">
<system:String>Template 1</system:String>
<system:String>Template 2</system:String>
</ComboBox>
</StackPanel>
<ContentControl Content="{Binding Path=Model}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TypeSelector, Path=SelectedItem}" Value="Template 2">
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template2}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template1}" />
</Style>
</ContentControl.Style>
</ContentControl>
</DockPanel>
</Window>
The notable points of the view are
The DataContext is initialised on the Window element, allowing for auto-complete on our binding expressions
The definition of 2 template to display 2 different views of the data.
The ComboBox is populated with a list of strings and has a default selection of the first element.
The ContentControl has its content bound to the Model exposed via the ViewModel
The default DataTemplate is the first template with a ComboBox.
The Trigger in the ContentControl's style will change the ContentTemplate if the SelectedItem of the ComboBox is changed to 'Template 2'
Implied facts are
If the SelectedItem changes back to 'Template 1', the style will revert the the ContentTemplate back to the default, ie MyModel1Template1
If there were a need for 3 separate displays, create another DataTemplate, add a string to the ComboBox and add another DataTrigger.
NOTE: This is the complete source to my example. Create a new C#/WPF project with the same classes and past the code in. It should work.
I hope this helps.

Using MVVM Light EventToCommand In DataTemplate

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.

How to loop through ItemsControl in WPF?

How can I loop through this ItemsControl and change it's TextBlock background in this Xaml's code behind page on some mouse event. I am new to WPF.
<ItemsControl ItemsSource="{Binding Path= HeaderList}" Name="Headers">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Name="Data" Text="{Binding }" Width="100" HorizontalAlignment="Left" PreviewMouseLeftButtonDown="MouseLeftButtonDown_Handler"
MouseEnter="MouseEnter_Handler" MouseLeave="MouseLeave_Handler">
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Thanks in advance!!
Actually my requirement is to change individual TextBlock's background color on different mouse events. So i need to get access of TextBlock in code behind and depending upon login I can change that Textblock's background color accordingly. So i think need to iterate ItemsControl. in case if I bind Background Property then all on property change would have effect on all the Textblocks in that ItemsControl. I don't want it in this way. I want to set and change every individual textblock's color differently.
I have access to single one in the eventhandlers that caused that event, but I want to access all the textblocks that are in itemscontrol and change their color acoording to some logic
Solution with background binding like axelle suggested:
You can iterate through the items in the HeaderList and set the background-property.
The Header class must implement the INotifyPropertyChanged Interface
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Path=HeaderList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Background="{Binding Background}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public partial class MainWindow : Window
{
public class Header : NotificationObject
{
public string Text { get; set; }
public Brush Background { get; set; }
}
public IList<Header> HeaderList { get; set; }
public MainWindow()
{
HeaderList = new List<Header>
{
new Header {Text = "header1", Background = Brushes.Red},
new Header {Text = "header2", Background = Brushes.Blue},
new Header {Text = "header3", Background = Brushes.Chartreuse},
};
DataContext = this;
InitializeComponent();
}
}
If I understand your question correctly, you'd want to bind the TextBlock background to a value in your datacontext, and change that value on your mouse event.
don't loop through the itemscontrol, better use a Trigger to apply the changes to your textblock :)
<ItemsControl ItemsSource="{Binding Path= HeaderList}" Name="Headers">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>

How to add new user control in TabControl.ContentTemplate?

I am little stuck with adding new instances of a usercontrol in a TabControl.ContentTemplate?
My Xaml is here:
<TabControl ItemsSource="{Binding Tables}">
<TabControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type uc:mytest1}">
<uc:mytest1>
</uc:mytest1>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I am binding TabControl.ItemsSource property to an ObservableCollection and in the content template I am adding a user control, but when this app runs I am getting new items as TabItems but the content page is holding same user control, but I want new user controls to be added for each new TabItem.
I am very new to the WPF and may be I am doing a very basic mistake, kindly guide me.
The ControlTemplate determines the appearance of the elements of the tab control that are not part of the individual tab items. The ItemTemplate handles the content of the individual tab items. Additionally, a TabItem is a headered content control, which means it has two content type properties Content and Header with two separate templates ContentTemplate and HeaderTemplate. In order to be able to populate the tab items using binding, you need to style the TabItem using the above properties.
Example:
<Window x:Class="Example.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Window"
Title="Window2" Height="300" Width="300">
<Window.DataContext>
<Binding ElementName="Window" Path="VM"/>
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="TabItemHeaderTemplate">
<Grid>
<TextBlock Text="{Binding Header}"/>
<Ellipse Fill="Red" Width="40" Height="40" Margin="0,20,0,0"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TabItemContentTemplate">
<Ellipse Fill="Green"/>
</DataTemplate>
<Style x:Key="TabItemContainerStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate"
Value="{StaticResource TabItemHeaderTemplate}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate"
Value="{StaticResource TabItemContentTemplate}"/>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource TabItemContainerStyle}"/>
</Grid>
</Window>
The code behind:
public partial class Window2 : Window
{
public TabControlVM VM { get; set; }
public Window2()
{
VM = new TabControlVM();
InitializeComponent();
}
}
And the view model classes:
public class TabControlVM
{
public ObservableCollection<TabItemVM> Items { get; set; }
public TabControlVM()
{
Items = new ObservableCollection<TabItemVM>();
Items.Add(new TabItemVM("tabitem1"));
Items.Add(new TabItemVM("tabitem2"));
Items.Add(new TabItemVM("tabitem3"));
Items.Add(new TabItemVM("tabitem4"));
}
}
public class TabItemVM
{
public string Header { get; set; }
public TabItemVM(string header)
{
Header = header;
}
}
Saurabh, When you set Template, usually DataTemplate, ControlTemplate etc, the visual elements inside these templates are reused in WPF with concept of UI Virtualization. TabControl typically displays only one item at a time, so it does not create new Visual Item for every tab item, instead it only changes that DataContext and refreshes bindings of "Selected Visual Item". Its loaded/unloaded events are fired, but the object is same always.
You can use loaded/unload events and write your code accordingly that your "Visual Element" which is your usercontrol, so that control should be stateless and is not dependent on old data. When new DataContext has applied you should refresh everything.
DataContextChanged, Loaded and Unloaded events can help you remove all dependencies on old data.
Otherwise, you an create a new TabItem manually with your UserControl as its Child and add it in TabControl instead of adding Data Items.
Adding TabItems manually will create new control for every item and in selected area different elements will appear based on selection.

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