How to show sub-menu of a MainMenu programmatically?

1k Views Asked by At

I need programmatically to show a sub-menu of main menu on a form in Winform .NΕΤ application (classes MainMenu with MenuItem).

  • E.g. show sub-menu with all items of menu item File.
  • E.g. same as pressing Alt-F if menu item text is &File.

I have tried to call OnPopup(), PerformClick(), PerformSelect(), and sending WM_MenuSelect message; sub-menu does not open.

Is there a way to do it?

2

There are 2 best solutions below

1
Reza Aghaei On BEST ANSWER

To show a menu item of a legacy MainMenu component, you need to call TrackPopupMenuEx and pass the menu item handle to it. To show it in the correct location, as when you click on the menu item, get the menu item rectangle using GetMenuItemRect.

enter image description here enter image description here

Here is the code:

[DllImport("user32.dll")]
static extern int TrackPopupMenuEx(IntPtr hmenu, uint fuFlags,
    int x, int y, IntPtr hwnd, IntPtr lptpm);
[DllImport("user32.dll")]
static extern bool GetMenuItemRect(IntPtr hWnd, IntPtr hMenu,
    uint uItem, out RECT lprcItem);
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public struct RECT { public int Left, Top, Right, Bottom; }
const int TPM_RIGHTBUTTON = 0x2;
const int TPM_RETURNCMD = 0x100;
const int WM_SYSCOMMAND = 0x112;

public void ShowSubMenu(MenuItem menuItem, bool asContextMenu = false)
{
    var mainMenu = menuItem.GetMainMenu();
    var form = mainMenu.GetForm();
    var x = 0; var y = 0;
    if (asContextMenu)
    {
        x = MousePosition.X; y = MousePosition.Y;
    }
    else
    {
        GetMenuItemRect(form.Handle, mainMenu.Handle, 
            (uint)menuItem.Index, out RECT rect);
        x = rect.Left; y = rect.Bottom;
    }
    var command = TrackPopupMenuEx(menuItem.Handle, TPM_RETURNCMD | TPM_RIGHTBUTTON,
        x, y, form.Handle, IntPtr.Zero);
    if (command > 0)
        SendMessage(form.Handle, WM_SYSCOMMAND, command, IntPtr.Zero);
}

To use it, just call it this way:

ShowSubMenu(fileMenuItem);

If you call it by passing false to showAxContextMenu, it will show the sub menu in the mouse position:

ShowSubMenu(fileMenuItem, true);

Note: It's recommended to use a MenuStrip instead of a MainMenu.

0
AudioBubble On

You need to clone the required menu items to a new ContextMenu component in the Form's constructor or Load event and attach it to the ContextMenu(not ContextMenuStrip) property of the target control.

For example, to clone the New, Open, and Save items from the File menu into a new ContextMenu:

var cmn = new ContextMenu();

cmn.MenuItems.AddRange(
    new[]
    {
        mnuFileNew.CloneMenu(),
        mnuFileOpen.CloneMenu(),
        mnuFileSave.CloneMenu()
    }
);

Or, if you need them all:

mnuFile.MenuItems.Cast<MenuItem>().ToList()
    .ForEach(m => cmn.MenuItems.Add(m.CloneMenu()));

Note that, the sub items will be cloned too if any.

And assign it to a ContextMenu property:

this.ContextMenu = cmn;

Now right click on the form and the new ContextMenu will pop up, click an item and the same code will be executed.