93

I have a ContextMenuStrip that is assigned to several different listboxes. I am trying to figure out when the ContextMenuStrip is clicked what ListBox it was used on. I tried the code below as a start but it is not working. The sender has the correct value, but when I try to assign it to the menuSubmitted it is null.

private void MenuViewDetails_Click(object sender, EventArgs e)
{
    ContextMenu menuSubmitted = sender as ContextMenu;
    if (menuSubmitted != null)
    {
        Control sourceControl = menuSubmitted.SourceControl;
    }
}

Any help would be great. Thanks.

Using the assistance below, I figured it out:

private void MenuViewDetails_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
            if (menuItem != null)
            {
                ContextMenuStrip calendarMenu = menuItem.Owner as ContextMenuStrip;

                if (calendarMenu != null)
                {
                    Control controlSelected = calendarMenu.SourceControl;
                }
            }
        }
Taryn
  • 242,637
  • 56
  • 362
  • 405
  • thanks for the solution i was looking for. i had the same problem. but i suggest not nesting all those `if` statements and using `if (menuItem == null) return;` if you are like me and don't want your code that handles it to be nested an extra needless 2 levels. – Shawn Kovac Nov 12 '13 at 20:15
  • It seems to me the solution you provide could be coded more concisely: `Control controlSelected = ((sender as ToolStripMenuItem)?.Owner as ContextMenuStrip)?.SourceControl;` – Stewart Apr 30 '21 at 15:57

6 Answers6

138

For a ContextMenu:

The problem is that the sender parameter points to the item on the context menu that was clicked, not the context menu itself.

It's a simple fix, though, because each MenuItem exposes a GetContextMenu method that will tell you which ContextMenu contains that menu item.

Change your code to the following:

private void MenuViewDetails_Click(object sender, EventArgs e)
{
    // Try to cast the sender to a MenuItem
    MenuItem menuItem = sender as MenuItem;
    if (menuItem != null)
    {
        // Retrieve the ContextMenu that contains this MenuItem
        ContextMenu menu = menuItem.GetContextMenu();

        // Get the control that is displaying this context menu
        Control sourceControl = menu.SourceControl;
    }
}

For a ContextMenuStrip:

It does change things slightly if you use a ContextMenuStrip instead of a ContextMenu. The two controls are not related to one another, and an instance of one cannot be casted to an instance of the other.

As before, the item that was clicked is still returned in the sender parameter, so you will have to determine the ContextMenuStrip that owns this individual menu item. You do that with the Owner property. Finally, you'll use the SourceControl property to determine which control is displaying the context menu.

Modify your code like so:

private void MenuViewDetails_Click(object sender, EventArgs e)
{
     // Try to cast the sender to a ToolStripItem
     ToolStripItem menuItem = sender as ToolStripItem;
     if (menuItem != null)
     {
        // Retrieve the ContextMenuStrip that owns this ToolStripItem
        ContextMenuStrip owner = menuItem.Owner as ContextMenuStrip;
        if (owner != null)
        {
           // Get the control that is displaying this context menu
           Control sourceControl = owner.SourceControl;
        }
     }
 }
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • @bluefeet: Then you have something else wrong. I just tested this code with three different listboxes, and everything worked as expected. Post some more repro code. – Cody Gray - on strike Feb 03 '11 at 13:02
  • 2
    @bluefeet: I've updated the code in my answer. There's a big difference between the `ContextMenu` and `ContextMenuStrip`. (Ah, and I see you've already figured it out. Well, all the better to learn things on your own!) – Cody Gray - on strike Feb 03 '11 at 13:20
  • For a ToolStripItem you could do e.SelectedItem and that will return a ToolStripItem so casting is not necessary. – konrad Aug 22 '16 at 18:31
  • 1
    I used the Opening event to record the SourceControl that opened the menu to a local variable, and then referenced that when handling item clicks. – QuickDanger Jun 08 '17 at 16:22
  • 1
    @QuickDanger Yeah, `SourceControl` is sadly null at the moment a `Click` event of a `ToolStripItem` sub-item of `ContextMenuStrip` is fired. It seems that the `ContextMenuStrip`'s `Closed` event fires _before_ that `Click` event, which is probably what causes the problem; I assume the property is cleared after the menu 'closes'. – Nyerguds Jun 02 '18 at 13:23
  • 1
    @CodyGray Actually, if the tree is deeper you have to loop up the chain of `OwnerItem` properties until you find a `ToolStripItem` that has a `ContextMenuStrip` in its `Owner` property. But as I just commented, it doesn't work; the `SourceControl` on the context menu will be null. You said you can't reproduce it though... maybe the problem only occurs with menus deeper than one level? Mine was two sub-levels deep. – Nyerguds Jun 02 '18 at 13:34
  • It might be a good idea to make something like this into an extension method – jrh Jun 07 '18 at 17:08
  • What if the menu item is activated via a keyboard shortcut? The menu itself never opens, and the SourceControl is null. – Darryl Sep 11 '18 at 23:45
4

Older post, but in case someone like myself comes across it:

For a ContextMenuStrip, the above didn't work for me, but it led to finding what did.

void DeleteMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
    ContextMenuStrip menu = sender as ContextMenuStrip;
    Control sourceControl = menu.SourceControl;
    MessageBox.Show(sourceControl.Name);
}

This gave me the expected control's name. You can put in validation etc with if statements, I'm just posting to get to the point.

Mark
  • 3,273
  • 2
  • 36
  • 54
seanu13
  • 171
  • 1
  • 1
  • 5
  • This only works with the _direct_ items in a `ContextMenu`. The problem is that `ItemClicked` doesn't fire when clicking _sub-menu items_; they need their own `Click` event which would have the item itself as sender, not the menu. – Nyerguds Jun 02 '18 at 13:26
4

I had great difficulty getting any of this code to work. This is the simplest solution I could find:

For A ContextMenuStrip:

    Control _sourceControl = null;
    private void contextMenuStrip_Opened(object sender, EventArgs e)
    {
        _sourceControl = contextMenuStrip.SourceControl;
    }

    private void contextMenuItem_Click(object sender, EventArgs e)
    {
        var menuItem = (ToolStripMenuItem)sender;

        _sourceControl.Text = menuItem.Text;
        MessageBox.Show(menuItem.Name);
        MessageBox.Show(sourceControl.Name);
    }
Nick Allan
  • 387
  • 4
  • 10
2

Cast sender to ToolStripItem to reach Owner which will be a ToolStrip that doesn't have a SourceControl property.

Cast Owner to ContextMenuStrip to reach SourceControl.

Control sc = ((ContextMenuStrip)((ToolStripItem)sender).Owner).SourceControl;
Dan
  • 51
  • 5
1

How about just using ActiveForm.ActiveControl, in this example from a C1 grid:

C1.Win.FlexGrid.C1FlexGrid fg = frmMain.ActiveForm.ActiveControl as C1.Win.FlexGrid.C1FlexGrid;
MX313
  • 129
  • 1
  • 9
0

The easiest solution would be:

Control parentControl = ((sender as MenuItem).GetContextMenu()).SourceControl;