Creating context menus on menus


Last week we looked at menu drag/drop. Another little-used menu feature added in Windows 2000 is the ability to show context menus on menus. The message is WM_MENU­RBUTTON­UP and the flag is TPM_RECURSE. Let’s demonstrate with a simple program.

Start with the scratch program, and add the Move­Menu­Item function just so our context menu can do something.

// resource header file
#define IDM_MAIN 1
#define IDM_POPUP 2
#define IDC_MOVEUP 200
#define IDC_MOVEDOWN 201

// resource file
1 MENU PRELOAD
BEGIN
    POPUP "&Test"
    BEGIN
        MENUITEM "&Red",    100
        MENUITEM "&Orange", 101
        MENUITEM "&Yellow", 102
        MENUITEM "&Green",  103
        MENUITEM "&Blue",   104
        MENUITEM "&Violet", 105
    END
END

2 MENU PRELOAD
BEGIN POPUP ""
    BEGIN
        MENUITEM "Move &Up",   IDC_MOVEUP
        MENUITEM "Move &Down", IDC_MOVEDOWN
        MENUITEM SEPARATOR
        MENUITEM "&Cancel",    IDCANCEL
    END
END

// scratch.cpp
#define HANDLE_WM_MENURBUTTONUP(hwnd, wParam, lParam, fn) \
    ((fn)((hwnd), (UINT)(wParam), (HMENU)(lParam)), 0L)

void OnMenuRButtonUp(HWND hwnd, UINT uPos, HMENU hmenu)
{
 if (hmenu == GetSubMenu(GetMenu(hwnd), 0)) {
  HMENU hmenuPopup = LoadMenu(g_hinst, MAKEINTRESOURCE(IDM_POPUP));
  if (hmenuPopup) {
   if (uPos == 0) {
    EnableMenuItem(hmenuPopup, IDC_MOVEUP, MF_DISABLED | MF_GRAYED);
   }
   if (uPos == GetMenuItemCount(hmenu) - 1) {
    EnableMenuItem(hmenuPopup, IDC_MOVEDOWN, MF_DISABLED | MF_GRAYED);
   }
   DWORD dwPos = GetMessagePos();
   UINT idCmd = TrackPopupMenuEx(GetSubMenu(hmenuPopup, 0),
                 TPM_RECURSE | TPM_RETURNCMD,
                 GET_X_LPARAM(dwPos),
                 GET_Y_LPARAM(dwPos), hwnd, NULL);
   switch (idCmd) {
    case IDC_MOVEUP:
     MoveMenuItem(hmenu, uPos, uPos - 1);
     break;
    case IDC_MOVEDOWN:
     MoveMenuItem(hmenu, uPos, uPos + 2);
     break;
   }
   DestroyMenu(hmenuPopup);
  }
 }
}

    HANDLE_MSG(hwnd, WM_MENURBUTTONUP, OnMenuRButtonUp);

// InitApp function
    wc.lpszMenuName = MAKEINTRESOURCE(IDM_MAIN);

When we receive the WM_MENU­RBUTTON­UP message and confirm that the menu is the one we support, we create the popup menu and display it at the mouse location (obtained via Get­Message­Pos) with the TPM_RECURSE flag, indicating that this is a pop-up menu for a pop-up menu. (We also use TPM_RETURN­CMD, but that’s nothing new.) If the user chose to move the item up or down, we move it up or down.

That’s all. There really isn’t much here, but I figured I’d just write a sample program just to show how it’s done.

Comments (12)
  1. Xzibit says:

    Yo dawg, I heard you like clicking, so we put a menu in your menu, so you can click while you click.

  2. Joshua says:

    As poor as that meme is, in this particular place it's semi-fitted.

  3. Douglas says:

    So what if the user presses the context menu key (Shift+F10 if they don't have one)? Is there a WM_MENUCONTEXTMENU message?

    I ask because I've had to scold a number of people for using WM_RBUTTONUP to display a context menu instead of WM_CONTEXTMENU. This promotes more ..RBUTTONUP incorrectness, and a continuation of the trend toward anti-keyboard friendly applications.

  4. Peter says:

    @Douglas AFAIK, there is no such message. I use "context menus on menu" in my application, and I was unable to handle Shift+F10 or the context menu key. There is no such message and no documented way to intercept keystrokes and handle it.

    Raymond, could you tell us why there is no keyboard interface?

    [Because the keystrokes which call up a context menu also dismiss it! Maybe that's why nobody uses this feature. -Raymond]
  5. asf says:

    Why use MF_DISABLED | MF_GRAYED, do you really need both?

  6. Mike Dunn says:

    The Start menu in XP and 7 does show a nested context menu when you press Shift+F10 or the menu key.  Interestingly, in Vista, only Shift+F10 works.  I imagine that they're using a keyboard hook (or message hook, or something similar) to implement that feature.

    [Or they're not using this feature in the first place. -Raymond]
  7. Neil says:

    I don't see why you shouldn't be able to use the context menu key to open a context menu on a main menu. (Ironically in Firefox you can even use the keyboard to open a context menu on a toolbarbutton menu, such as for a bookmark folder, even though you can't actually open the toolbarbutton menu itself without a mouse. Of course normally if you wanted to do this you would open bookmarks from the main menu.)

  8. ArlieD says:

    This just seems like a bad idea to start with.  I cannot imagine any ordinary user getting this right.  I help a lot of older relatives use computers, and context menus really blow their minds to begin with.  Accepting a context menu on a context menu is just crazy.  It's also very inconsistent with simply choosing an entry from a context menu.  Raymond, for once, this article would have been improved by not being published.

  9. Chris Long says:

    ArlieD:  I disagree – one place where this is a very natural thing to do is on Firefox's Bookmarks menu.  Let's say I want to delete a bookmark – how would I try?  The first thing I tried was to open the Bookmarks menu and right-click on the relevant bookmark.  Bingo – there's the Delete option, right there.  Very intuitive.

  10. kero says:

    Dear Raymond, still on the menu.

    I'd like to read your story about MN_-messages, which in MSDN mentioned only one: MN_GETHMENU.

    [You must be new here. -Raymond]
  11. kero says:

    Dear Raymond, I apologize if unwittingly violated the rules of your blog link in my example.

    But no, I'm not "new here" but a longtime reader of your blog, in confirmation – blogs.msdn.com/…/10110077.aspx  :)

  12. ywq says:

    I have a question:I can drag a menu item,and drop it on the same menu.But how can I drag something like a shortcut,and drop it on menu? Like the IE's favorite menu.

Comments are closed.