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:
ItemsSource=ObservableCollection<FooViewModel>
, x:Name=MainTabs
)
Why am I getting this error and how can I make the context menu command to "target" the FooViewModel
's HelpExecuted
method?
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!
}
}
Does this help?
<ContextMenu>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=HelpExecuted}" />
</Style>
</ContextMenu.ItemContainerStyle>
<MenuItem Header="Help" />
</ContextMenu>
I'm afraid MatthiasG beat me to it. My solution is similar:
Here the Help command is handled by the tab item's view model. It would be simple to pass a reference to the TestViewModel to each of the TestItemViewModel and have ShowHelp call back into TestViewModel if required.
public class TestViewModel
{
public TestViewModel()
{
Items = new List<TestItemViewModel>{
new TestItemViewModel(), new TestItemViewModel() };
}
public ICommand HelpCommand { get; private set; }
public IList<TestItemViewModel> Items { get; private set; }
}
public class TestItemViewModel
{
public TestItemViewModel()
{
// Expression Blend ActionCommand
HelpCommand = new ActionCommand(ShowHelp);
Header = "header";
}
public ICommand HelpCommand { get; private set; }
public string Header { get; private set; }
private void ShowHelp()
{
Debug.WriteLine("Help item");
}
}
The xaml
<Window.Resources>
<ContextMenu x:Key="FooMenu">
<MenuItem Header="Help" Command="{Binding HelpCommand}"/>
</ContextMenu>
<DataTemplate x:Key="ItemTemplate">
<!-- context menu on header -->
<TextBlock Text="{Binding Header}" ContextMenu="{StaticResource FooMenu}"/>
</DataTemplate>
<DataTemplate x:Key="ContentTemplate">
<Grid Background="#FFE5E5E5">
<!-- context menu on data grid -->
<DataGrid ContextMenu="{StaticResource FooMenu}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<WpfApplication2:TestViewModel/>
</Window.DataContext>
<Grid>
<TabControl
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource ItemTemplate}"
ContentTemplate="{StaticResource ContentTemplate}" />
</Grid>
Alternative view models so that the help command is directed to the root view model
public class TestViewModel
{
public TestViewModel()
{
var command = new ActionCommand(ShowHelp);
Items = new List<TestItemViewModel>
{
new TestItemViewModel(command),
new TestItemViewModel(command)
};
}
public IList<TestItemViewModel> Items { get; private set; }
private void ShowHelp()
{
Debug.WriteLine("Help root");
}
}
public class TestItemViewModel
{
public TestItemViewModel(ICommand helpCommand)
{
HelpCommand = helpCommand;
Header = "header";
}
public ICommand HelpCommand { get; private set; }
public string Header { get; private set; }
}
A very simple implementation of ActionCommand
public class ActionCommand : ICommand
{
private readonly Action _action;
public ActionCommand(Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
// not used
public event EventHandler CanExecuteChanged;
}
You are getting this error because CommandBinding.Executed is not dependency property so you cannot bind to it.
Instead, use ResourceDictionary code behind to specify event handler for CommandBinding.Executed event, and in the event handler code call FooViewModel.HelpExecuted() method like this:
MainWindowResourceDictionary.xaml
<ResourceDictionary x:Class="WpfApplication.MainWindowResourceDictionary"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<DataTemplate DataType="{x:Type local:FooViewModel}">
<Grid>
<DataGrid ContextMenu="{DynamicResource FooContextMenu}"/>
</Grid>
</DataTemplate>
<ContextMenu x:Key="FooContextMenu">
<ContextMenu.CommandBindings>
<CommandBinding Command="Help" Executed="HelpExecuted"/>
</ContextMenu.CommandBindings>
<MenuItem Command="Help"/>
</ContextMenu>
</ResourceDictionary>
MainWindowResourceDictionary.xaml.cs
public partial class MainWindowResourceDictionary : ResourceDictionary
{
public MainWindowResourceDictionary()
{
InitializeComponent();
}
private void HelpExecuted(object sender, ExecutedRoutedEventArgs e)
{
var fooViewModel = (FooViewModel)((FrameworkElement)e.Source).DataContext;
fooViewModel.HelpExecuted();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With