Benjin
Benjin

Reputation: 2409

Programmatically add a context menu whose Click handler knows which item was right-clicked

I'm dynamically generating a tree (TreeViewItems) and want to add the same context menu to each item in the tree. Because all of the context menus will be the same, I figured I could make one, and apply it to each TreeViewItem. (Maybe this is a bad idea?) Seems like this should work as long as the Click handler can figure out which TreeViewItem's context menu was opened.

I tried combining SO answers from here (getting the right-clicked object) and here (programmatically adding a binding) and came up with this:

ContextMenu carContextMenu;

public MainWindow()
{
    InitializeComponent();
    Initialize();
    ConstructTree();
}

void ConstructTree()
{
    string[] carNames = {"Mustang", "Viper", "Jetta"};

    foreach (string car in carNames)
    {
        TreeViewItem carNode = new TreeViewItem();
        carNode.Header = car;
        carNode.ContextMenu = carContextMenu;

        CarTree.Items.Add(carNode);
    }
}

void Initialize()
{
    carContextMenu= new ContextMenu();
    MenuItem newQuery = new MenuItem();
    newQuery.Header = "Drive car...";

    Binding b = new Binding("Parent");
    b.RelativeSource = RelativeSource.Self;

    newQuery.SetBinding(MenuItem.CommandParameterProperty, b);
    newQuery.Click += NewQuery_Click;

    carContextMenu.Items.Add(newQuery);
}

void NewQuery_Click(object sender, RoutedEventArgs e)
{
    MenuItem mi = sender as MenuItem;
    if (mi != null)
    {
        ContextMenu cm = mi.CommandParameter as ContextMenu; // *****
        if (cm != null)
        {
            TreeViewItem node = cm.PlacementTarget as TreeViewItem;
            if (node != null)
            {
                Console.WriteLine(node.Header); // car name, ideally
            }
        }
    }
}

At runtime, when it gets to the line with asterisks, mi.CommandParameter is null, so it skips the rest of the method. What's going on with my approach? Honestly, I'm a little surprised that the right-clicked item isn't an intrinsic part of the event handler arguments, given how often you'd want to know what was clicked. Tree items aren't necessary selected when they're right-clicked, so checking that isn't a reliable method... plus it'd just be a hacky workaround.

Thanks!

Upvotes: 5

Views: 9304

Answers (1)

Benjin
Benjin

Reputation: 2409

Naturally, it turns out I was overcomplicating things and the links I was following were either incorrect, out-of-date, or (most likely) I misread some portion of their scenario and there was something that didn't actually apply to me.

I didn't need any Binding on the MenuItem itself and simply should have been looking at myMenuItem.Parent.PlacementTarget the whole time. Working code below:

void Initialize()
{
    carContextMenu= new ContextMenu();
    MenuItem newQuery = new MenuItem();
    newQuery.Header = "Drive car...";

    newQuery.Click += NewQuery_Click;

    carContextMenu.Items.Add(newQuery);
}

void NewQuery_Click(object sender, RoutedEventArgs e)
{
    MenuItem mi = sender as MenuItem;
    if (mi != null)
    {
        ContextMenu cm = mi.Parent as ContextMenu;
        if (cm != null)
        {
            TreeViewItem node = cm.PlacementTarget as TreeViewItem;
            if (node != null)
            {
                Console.WriteLine(node.Header);
            }
        }
    }
}  

Upvotes: 3

Related Questions