How to add ContextMenuStrip to ToolStripMenuItem

2019-05-27 01:12发布

问题:

I want that when I click on a menu item, display a context menu with items such as "delete", "rename", etc.

How to bind itself a context menu when you right-click on the menu item?

回答1:

The first idea jumping in my mind was hook up some MouseDown event on the ToolStripMenuItem and show the second ContextMenuStrip at the mouse position in screen coordinates. But it's not such simple. The problem is doing so will require hooking up the event for every items, on that event somehow showing the second ContextMenuStrip will close the current ContextMenuStrip (even we add some Closing event handler and set e.Cancel = true;). It's a little tricky here. We could think of the MouseDown event of the current ContextMenuStrip but in fact this event is hardly fired because all the items lie on top of the ContextMenuStrip. That made me think of the deeper stage where we can catch the WM_RBUTTONDOWN and run code there. We can customize the ContextMenuStrip to catch that message in WndProc or we can use a custom NativeWindow. I would like to use a NativeWindow here. It's time for the code (working perfectly):

public class NativeContextMenuStrip : NativeWindow
{
    public class ShowContextMenuEventArgs : EventArgs {
        public ToolStripDropDown ContextMenuToShow {get; set;}
    }
    public delegate void ShowContextMenuEventHandler(ShowContextMenuEventArgs e);
    public event ShowContextMenuEventHandler ShowContextMenu;
    private Color previousItemBackColor;
    public ToolStripItem SourceItem { get; set; }
    bool keepOpen;
    protected override void WndProc(ref Message m)
    {                       
        base.WndProc(ref m);            
        if (m.Msg == 0x204) {//WM_RBUTTONDOWN
            OnShowContextMenu(new ShowContextMenuEventArgs());
        }
    }
    protected virtual void OnShowContextMenu(ShowContextMenuEventArgs e)
    {
        var handler = ShowContextMenu;
        if (handler != null)
        {
            handler(e);
            if (e.ContextMenuToShow != null)
            {
                ContextMenuStrip toolStrip = (ContextMenuStrip)Control.FromHandle(Handle);
                Point client = toolStrip.PointToClient(Control.MousePosition);
                SourceItem = toolStrip.GetItemAt(client);
                previousItemBackColor = SourceItem.BackColor;
                SourceItem.BackColor = SystemColors.MenuHighlight;                    
                e.ContextMenuToShow.Closed -= restoreItemState;
                e.ContextMenuToShow.Closed += restoreItemState;
                keepOpen = true;
                e.ContextMenuToShow.Show(Control.MousePosition);
                keepOpen = false;
            }
        }                
    }
    protected override void OnHandleChange()
    {
        base.OnHandleChange();
        ContextMenuStrip toolStrip = Control.FromHandle(Handle) as ContextMenuStrip;
        if (toolStrip != null)
        {
            toolStrip.Closing += toolStripClosing;
        }
    }
    private void restoreItemState(object sender, EventArgs e)
    {
        SourceItem.BackColor = previousItemBackColor;
        SourceItem.Owner.Show();
    }
    private void toolStripClosing(object sender, ToolStripDropDownClosingEventArgs e)
    {
        e.Cancel = keepOpen;
    }
}

Usage:: The important event is ShowContextMenu, hook up this event and set the ContextMenuStrip you want to show. That's all. Here is the detail:

public partial class Form1 : Form {
  public Form1(){
     InitializeComponent();
     //suppose you have a main ContextMenuStrip and a sub ContextMenuStrip
     //try adding some items for both
     ContextMenuStrip = new ContextMenuStrip();
     ContextMenuStrip.Items.Add("Item 1");
     ContextMenuStrip.Items.Add("Item 2");
     //sub ContextMenuStrip
     var subMenu = new ContextMenuStrip();
     subMenu.Items.Add("Delete");
     subMenu.Items.Add("Rename");  
     ContextMenuStrip.HandleCreated += (s,e) => {
       nativeMenu.AssignHandle(ContextMenuStrip.Handle);
       nativeMenu.ShowContextMenu += (ev) => {
          ev.ContextMenuToShow = subMenu;
       };
     };
  }
  NativeContextMenuStrip nativeMenu = new NativeContextMenuStrip();
}

To get the item clicking on which shows the sub ContextMenuStrip, you can access the SourceItem of the NativeContextMenuStrip.