How to implement commands to use ancestor methods in WPF?
c#, wpf, xaml
Solution
Unfortunately you cannot bind `Executed` for a `ContextMenu` as it is an event. An additional problem is that the `ContextMenu` does not exist in the `VisualTree` the rest of your application exists. There are solutions for both of this problems.
First of all you can use the `Tag` property of the parent control of the `ContextMenu` to pass-through the `DataContext` of your application. Then you can use an `DelegateCommand` for your `CommandBinding` and there you go. Here's a small sample showing `View`, `ViewModel` and the `DelegateCommand` implementation you would have to add to you project.
DelegateCommand.cs
public class DelegateCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{ }
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
execute(parameter);
}
#endregion
}
MainWindowView.xaml
<Window x:Class="Application.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindowView" Height="300" Width="300"
x:Name="MainWindow">
<Window.Resources>
<ResourceDictionary>
<ContextMenu x:Key="FooContextMenu">
<MenuItem Header="Help" Command="{Binding PlacementTarget.Tag.HelpExecuted, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding FooViewModels}" x:Name="MainTabs">
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid ContextMenu="{DynamicResource FooContextMenu}" Tag="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MainWindowView.xaml.cs
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
MainWindowViewModel.cs
public class MainWindowViewModel
{
public ObservableCollection<FooViewModel> FooViewModels { get; set; }
public MainWindowViewModel()
{
FooViewModels = new ObservableCollection<FooViewModel>();
}
}
FooViewModel.cs
public class FooViewModel
{
public ICommand HelpExecuted { get; set; }
public FooViewModel()
{
HelpExecuted = new DelegateCommand(ShowHelp);
}
private void ShowHelp(object obj)
{
// Yay!
}
}
Problem
I have this context menu resource: ``` <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <ContextMenu x:Key="FooContextMenu"> <ContextMenu.CommandBindings> <CommandBinding Command="Help" Executed="{Binding ElementName=MainTabs, Path=HelpExecuted}" /> </ContextMenu.CommandBindings> <MenuItem Command="Help"> <MenuItem.Icon> <Image Source="../Resources/Icons/Help.png" Stretch="None" /> </MenuItem.Icon> </MenuItem> </ContextMenu> </ResourceDictionary> ``` I want to re-use it in two places. Firstly I'm trying to put it in a `DataGrid`: ``` <DataGrid ContextMenu="{DynamicResource FooContextMenu}">... ``` The `ContextMenu` itself works fine, but with the `Executed="..."` I have right now breaks the application and throws: A first chance exception of type 'System.InvalidCastException' occurred in PresentationFramework.dll Additional information: Unable to cast object of type 'System.Reflection.RuntimeEventInfo' to type 'System.Reflection.MethodInfo'. If I remove the entire `Executed="..."` definition, then the code works (and the command does nothing/grayed out). The exception is thrown as soon as I right click the grid/open the context menu. The `DataGrid` is placed under a few elements, but eventually they all are below a `TabControl` (called `MainTabs`) which has `ItemsSource` set to a collection of `FooViewModel`s, and in that `FooViewModel` I have a method `HelpExecuted` which I want to be called. Let's visualize: - TabControl (`ItemsSource=ObservableCollection<FooViewModel>`, `x:Name=MainTabs`) - Grid - More UI - DataGrid (with context menu set) Why am I getting this error and how can I make the context menu command to "target" the `FooViewModel`'s `HelpExecuted` method?