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?

Original source