Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: Binding a ContextMenu to an MVVM Command

Let's say I have a Window with a property returning a Command (in fact, it's a UserControl with a Command in a ViewModel class, but let's keep things as simple as possible to reproduce the problem).

The following works:

<Window x:Class="Window1" ... x:Name="myWindow">     <Menu>         <MenuItem Command="{Binding MyCommand, ElementName=myWindow}" Header="Test" />     </Menu> </Window> 

But the following does not work.

<Window x:Class="Window1" ... x:Name="myWindow">     <Grid>         <Grid.ContextMenu>             <ContextMenu>                 <MenuItem Command="{Binding MyCommand, ElementName=myWindow}" Header="Test" />             </ContextMenu>                     </Grid.ContextMenu>     </Grid> </Window> 

The error message I get is

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myWindow'. BindingExpression:Path=MyCommand; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

Why? And how do I fix this? Using the DataContext is not an option, since this problem occurs way down the visual tree where the DataContext already contains the actual data being displayed. I already tried using {RelativeSource FindAncestor, ...} instead, but that yields a similar error message.

like image 320
Heinzi Avatar asked Aug 27 '10 11:08

Heinzi


People also ask

What is command binding in WPF?

The command is the action to be executed. The command source is the object which invokes the command. The command target is the object that the command is being executed on. The command binding is the object which maps the command logic to the command.

What is Mvvm command?

Commands are an implementation of the ICommand interface that is part of the . NET Framework. This interface is used a lot in MVVM applications, but it is useful not only in XAML-based apps.


1 Answers

The problem is that the ContextMenu it not in the visual tree, so you basically have to tell the Context menu about which data context to use.

Check out this blogpost with a very nice solution of Thomas Levesque.

He creates a class Proxy that inherits Freezable and declares a Data dependency property.

public class BindingProxy : Freezable {     protected override Freezable CreateInstanceCore()     {         return new BindingProxy();     }      public object Data     {         get { return (object)GetValue(DataProperty); }         set { SetValue(DataProperty, value); }     }      public static readonly DependencyProperty DataProperty =         DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); } 

Then it can be declared in the XAML (on a place in the visual tree where the correct DataContext is known):

<Grid.Resources>     <local:BindingProxy x:Key="Proxy" Data="{Binding}" /> </Grid.Resources> 

And used in the context menu outside the visual tree:

<ContextMenu>     <MenuItem Header="Test" Command="{Binding Source={StaticResource Proxy}, Path=Data.MyCommand}"/> </ContextMenu> 
like image 187
Daniel Avatar answered Sep 30 '22 17:09

Daniel