I have a ItemsControl
control, in which there is ContextMenu
control.
The ItemsControl
has its ItemsSource
bound to a List<Person>
.
What I want to do is to bind a DisplayNameCommand
and a DisplaySurnameCommand
to their corresponding context menu item, where both commands are inside of a MainWindowViewModel
- not the bound Person
object!!!.
The important thing is that ContextMenu
needs to still have the ItemsSource
data context, as I need to acces the bound object property to include it in the command parameter.
ItemsControl:
<ItemsControl ItemsSource="{Binding PeopleList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Surname}"/>
<Image>
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Display Name"
Command="{Binding DisplaySurnameCommand}"
CommandParameter="{Binding Name}">
</MenuItem>
<MenuItem Header="Display Surname"
Command="{Binding DisplaySurnameCommand}"
CommandParameter="{Binding Surname}">
</MenuItem>
</ContextMenu>
</Image.ContextMenu>
</Image>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
MainWindowViewModel and Person class:
public class MainWindowViewModel
{
public MainWindowViewModel()
{
DisplayNameCommand = new RelayCommand(DisplayName);
DisplaySurnameCommand = new RelayCommand(DisplaySurname);
PeopleList = new List<Person>();
PeopleList.Add(new Person("Julie", "Adams"));
PeopleList.Add(new Person("Mack", "McMack"));
PeopleList.Add(new Person("Josh", "Broccoli"));
}
public List<Person> PeopleList { get; set; }
public void DisplayName(object message)
{
MessageBox.Show("Name: " + (string)message);
}
public void DisplaySurname(object message)
{
MessageBox.Show("Surname: "+ (string)message);
}
public RelayCommand DisplayNameCommand { get; }
public RelayCommand DisplaySurnameCommand { get; }
}
public class Person
{
public Person(string name, string surname)
{
Name = name;
Surname = surname;
}
public string Name { get; set; }
public string Surname { get; set; }
}
Also I know that it is possible to bind it to a Person
object and then point it to a viewmodel command, but that's not what I'm looking for.
I've created a demo project for this problem.
What I have tried
1. Specify Command for MenuItem in a DataTemplate (accepted answer)
<ContextMenu>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.DisplaySurname, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/>
<Setter Property="CommandParameter" Value="{Binding Name}"/>
</Style>
</ContextMenu.ItemContainerStyle>
<MenuItem Header="Display Name">
<MenuItem Header="Display Surname">
</ContextMenu>
So this is the closest that I got to the result, this does trigger the command but the problem is that there can be only 1 command set for all menu items.
If there could be a way to get around this by setting the name for the style or using something else than ItemContainerStyle it could work, but I couldn't come up with anything like that.
2. Set a relative command
<ContextMenu>
<MenuItem Header="Display Name"
Command="{Binding DataContext.DisplayNameCommand, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"
CommandParameter="{Binding Name}"/>
<MenuItem Header="Display Surname"
Command="{Binding DataContext.DisplaySurnameCommand, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"
CommandParameter="{Binding Surname}"/>
</ContextMenu>
This returns a binding error:
Cannot find source: RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1'.
3. Can't bind a ContextMenu action to a Command
The accepted answer first bound to a person object, and the updated solution didn't even work, but the second answer that I've tried:
<MenuItem Header="Display Surname"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DisplaySurnameCommand}"
CommandParameter="{Binding Surname}"/>
returned a binding error of:
Cannot find source: RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1'.
... and besides those three I've tried many more soltuions and variations, with little to no result.
I've spent a whole day trying to find a solution to this, please help me, wpf is taking away my sanity.
Ps. this is my first post so if you have any comments then let me know.