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?
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?
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
.