13

I have a ContextMenuStrip setup with two ToolStripItems. The second ToolStripItem has two additional nested ToolStripItems. I define this as:

ContextMenuStrip cms = new ContextMenuStrip();
ToolStripMenuItem contextJumpTo = new ToolStripMenuItem();
ToolStripMenuItem contextJumpToHeatmap = new ToolStripMenuItem();
ToolStripMenuItem contextJumpToHeatmapStart = new ToolStripMenuItem();
ToolStripMenuItem contextJumpToHeatmapLast = new ToolStripMenuItem();

cms.Items.AddRange(new ToolStripItem[] { contextJumpTo,
                                         contextJumpToHeatmap});
cms.Size = new System.Drawing.Size(176, 148);

contextJumpTo.Size = new System.Drawing.Size(175, 22);
contextJumpTo.Text = "Jump To (No Heatmapping)";

contextJumpToHeatmap.Size = new System.Drawing.Size(175, 22);
contextJumpToHeatmap.Text = "Jump To (With Heatmapping)";
contextJumpToHeatmap.DropDownItems.AddRange(new ToolStripItem[] { contextJumpToHeatmapStart, 
                                                                  contextJumpToHeatmapLast });

contextJumpToHeatmapStart.Size = new System.Drawing.Size(165, 22);
contextJumpToHeatmapStart.Text = "From Start of File";

contextJumpToHeatmapLast.Size = new System.Drawing.Size(165, 22);
contextJumpToHeatmapLast.Text = "From Last Data Point";

I then setup an event listener for the click events of the three ToolStripMenuItems that I want to respond to. Here are the methods (I only listed two of the three methods):

void contextJumpTo_Click(object sender, EventArgs e)
{
    // Try to cast the sender to a ToolStripItem 
    ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
    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 
            DataGridView dgv = owner.SourceControl as DataGridView;
            if (dgv != null)
                // DO WORK
        }
    } 
}

void contextJumpToHeatmapStart_Click(object sender, EventArgs e)
{
    // Try to cast the sender to a ToolStripItem 
    ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
    if (menuItem != null)
    {
        // Retrieve the ToolStripItem that owns this ToolStripItem 
        ToolStripMenuItem ownerItem = menuItem.OwnerItem as ToolStripMenuItem;
        if (ownerItem != null)
        {
            // Retrieve the ContextMenuStrip that owns this ToolStripItem 
            ContextMenuStrip owner = ownerItem.Owner as ContextMenuStrip;
            if (owner != null)
            {
                // Get the control that is displaying this context menu 
                DataGridView dgv = owner.SourceControl as DataGridView;
                if (dgv != null)
                    // DO WORK
            }
        }
    }
}

Here is the issue I have:

My contextJumpTo_Click method works perfectly fine. We get all the way down to where I determine which DataGridView the click came from and I can proceed. The contextJumpTo ToolStripMenuItem is, however, NOT a nested menu item on the ContextMenuStrip.

But my method for contextJumpToHeatmapStart_Click does not work right. When I get down to the line where I determine owner.SourceControl, the SourceControl is null and I cannot proceed. Now I know that this ToolStripMenuItem is nested under another one in my ContextMenuStrip, but why is the SourceControl property suddently null on my ContextMenuStrip?

How do I obtain the SourceControl for a nested ToolStripMenuItem for a ContextMenuStrip?

Michael Mankus
  • 4,628
  • 9
  • 37
  • 63
  • I just wasted a couple of hours on this. Amazingly only just fixed in .NET Framework 4.7.2. – Gavin Oct 23 '18 at 14:37

1 Answers1

12

I believe that's a bug.

I tried to crawl up the list of toolstrip parents to get to the ContextStripMenu owner, which worked, but the SourceControl property was always null.

It looks like the common work around is to set the control on the opening of the context menu:

private Control menuSource;

cms.Opening += cms_Opening;

void cms_Opening(object sender, CancelEventArgs e) {
  menuSource = ((ContextMenuStrip)sender).SourceControl;
}

Then your code basically turns into this:

DataGridView dgv = menuSource as DataGridView;
if (dgv != null) {
  // do work
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • I have settled upon the same conclusion. A bit upsetting to have to create a property for something where that property already exists. But not much I can do. – Michael Mankus Aug 23 '12 at 17:57
  • Also came to this conclusion just before seeing this answer... but in retrospect I think it's actually better to do it this way anyway. – BVernon Jan 21 '14 at 02:17
  • I get null this way too. – Russell Horwood Jun 26 '15 at 15:28
  • @RussellHorwood I can't see your code, but try putting a debug break on the opening event and inspect the SourceControl property to see what you are getting. – LarsTech Jun 26 '15 at 15:40
  • That's what I did, and yes had null there. How can a context menu strip open without a source? – Russell Horwood Jun 26 '15 at 15:53
  • @RussellHorwood I can't see your code. Try recreating the issue in a small project, and if it still happens, try posting it in a new question. Document your question with a link to this post. – LarsTech Jun 26 '15 at 15:57
  • This solution doesn't work if the end user users a keyboard accelerator for an item on the menu (e.g. if you've got the `ShortcutKeys` property set on a `ToolStripMenuItem` within the `ContextMenuStrip`) – Rowland Shaw Jan 07 '16 at 11:49
  • @RowlandShaw It still worked for me. You would have to provide more information for me to duplicate the scenario. – LarsTech Jan 07 '16 at 16:11
  • 1
    If you had a Textbox, with a context menu strip, with an item with an accelerator of (say) Ctrl+V - with focus in the textbox and not having invoked the context menu with mouse or keyboard, pressing Ctrl+V will call the event handler for that `ToolstripMenuItem`, but the `ContextMenuStrip`'s `Opening` event handler won't have been called. – Rowland Shaw Jan 07 '16 at 17:20
  • @RowlandShaw Control+V is usually paste, which is implemented by the TextBox control. – LarsTech Jan 07 '16 at 17:45
  • 1
    I know that, but we have end users that expect to see it on our context menu, and with it's shortcuts. In which case I have to wire the event up myself. – Rowland Shaw Jan 07 '16 at 20:10