Reputation: 2523
I have a context menu that is being populated from an ObservableCollection. I want the user to be able to click on any of those items, then a method is called passing the clicked item's text as a parameter.
I've started by following the answer to this question. However, I'm getting an error in my console output and my method is not being called.
System.Windows.Data Error: 40 : BindingExpression path error: 'FunctionToCall' property not found on 'object' ''MenuItem' (Name='myMenu')'. BindingExpression:Path=FunctionToCall; DataItem='MenuItem' (Name='myMenu'); target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
here is my xaml
<MenuItem Name="myMenu" Header="display text" ItemsSource="{Binding}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding FunctionToCall, RelativeSource={RelativeSource AncestorType=MenuItem}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
And my view model code
RelayCommand _command;
public ICommand FunctionToCall
{
get
{
if (_command == null)
{
_command = new RelayCommand(p => this.InnerMethod(p));
}
return _command ;
}
}
public void InnerMethod(object parameter)
{
....
The other answer suggests playing around with adding one or two DataContexts to the Binding, I've tried this and I still get the same error although it says DataContext property cannot be found instead of FunctionToCall.
I found the definition of RelayCommand here.
Upvotes: 0
Views: 1326
Reputation: 8813
The real problem is with your binding. Use the DataContext property of MenuItem to actually get to the ViewModel instance
<MenuItem Name="myMenu" Header="display text" ItemsSource="{Binding}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.FunctionToCall, RelativeSource={RelativeSource AncestorType=MenuItem}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
MenuItem will get ViewModel as DataContext. So actually we want..
MenuItem.DataContext.FunctionToCall
Hopefully you don't need the different menu items to bind to different commands else you have to change your design a little.
As Per Your Comments:
You'll need a List<MenuItem> MenuItems
to bind with ContextMenu ItemSource property as
public class MenuItem
{
public string Header { get; set; }
public ICommand Command { get; set; }
}
XAML:
<ContextMenu ItemsSource="{Binding MenuItems}" >
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" >
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
And add as many contextmenu item you want in your ViewModel AS YOU WANT.
Upvotes: 1
Reputation: 9827
This is how to do it.
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
xaml
<MenuItem Header="{Binding Item1}" Command="{Binding FunctionToCall}" CommandParameter="{Binding Header, RelativeSource={RelativeSource Self}}"/>
ViewModel
public class ViewModel
{
ICommand _cmd = new CustomCommand();
public ICommand FunctionToCall
{
get { return _cmd; }
set { _cmd = value; }
}
public string Item1 { get; set; }
public ViewModel() { Item1 = "1Header"; }
}
Command
public class CustomCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show(parameter.ToString());
}
}
So, in your case assuming you want to pass Header
of MenuItem
as parameter to your command, do following changes :
<Setter Property="Command" Value="{Binding FunctionToCall}"/>
<Setter Property="CommandParameter" Value="{Binding Header, RelativeSource={RelativeSource Self}}"/>
Upvotes: -1