Boumbles
Boumbles

Reputation: 2523

How do I add a command to items in dynamically generated ContextMenu

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

Answers (2)

Kylo Ren
Kylo Ren

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

AnjumSKhan
AnjumSKhan

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

Related Questions