Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find the menu item (if any) which opens a given HMENU when activated?

Tags:

c++

c

winapi

menu

mfc

I'd like to implement a function with the prototype

/* Locates the menu item of the application which caused the given menu 'mnu' to
 * show up.
 * @return true if the given menu 'mnu' was opened by another menu item, false
 * if not.
 */
bool getParentMenuItem( HMENU mnu, HMENU *parentMenu, int *parentMenuIdx );

Given a HMENU handle, I'd like to be able to find out which menu item (if any) in the application opened it. This is basically the reverse of the GetSubMenu function.

My current approach is to look into each HMENU of the top level windows of the application and check for whether I can find a menu item which would open the given sub menu when activated. I do this recursively, using GetMenuItemCount/GetSubMenu.

This is rather inefficient though, and it fails for menus which are opened by context menu items. Hence, I'm wondering:

Does anybody have a nice idea how to find the menu item (if any) which opens a given HMENU when activated?

UPDATE: An idea which just came to my mind; it should be possible (using the SetWindowsHookEx function) to install a hook which gets notified of all input events which happened in a menu. Whenever a menu item activation is detected, memorize the menu item (identified by a (HMENU,int) pair) and the HMENU which will get opened by the menu item in a global map. The getParentMenuItem function above could then simply perform a lookup into the map.

UPDATE to the update: The hooking idea described in the update above won't work as it is since it will of course only recognize menu item -> menu associations for items which have been activated at some point.

This feels a bit ugly though since it reqiures me to keep a lot of state (the map); are there any easier possibilities?

like image 641
Frerich Raabe Avatar asked Aug 24 '09 07:08

Frerich Raabe


2 Answers

You could try setting MENUINFO.dwMenuData to the parent menu handle for all menus you create in your application:

MENUINFO mi;
mi.cbSize = sizeof(MENUINFO);
mi.dwMenuData = (ULONG_PTR)<parent HMENU if this is a sub menu>
mi.fMask = MIM_MENUDATA;

SetMenuInfo(hCreatedMenu, &mi);

Then you only need to query this dwMenuData field in your function:

bool getParentMenuItem(HMENU mnu, HMENU *parentMenu, int *parentMenuIdx)
{
    MENUINFO mi;
    mi.cbSize = sizeof(MENUINFO);
    mi.fMask = MIM_MENUDATA;

    if (!GetMenuInfo(mnu,&mi) || mi.dwMenuData == 0)
        return false;

    *parentMenu = (HMENU)mi.dwMenuData;

    // not sure how or why you need the parentMenuIdx, but you should be able
    // to derive that from the parent HMENU

    return true;
}

Edit: If you don't have control over how all menus are created, you could use a WH_CALLWNDPROC hook to trap when a menu is first created. A good article (with source code) describes how this can be done - you could then look at trying to inject the parent HMENU into the created menu using the method described above.

like image 174
Alan Avatar answered Nov 17 '22 06:11

Alan


I just found the same need. I have my menus defined in the .rc file, and I want to gray out access to a popup menu if all of its subitems get grayed out. (You can argue that this inhibits discovery, but, in this particular case, it's what we need).

As previous responders mentioned, if you are creating menus programmatically, you can store an item's parent menu handle as ancillary information.

But for menus defined in the resource file using the POPUP keyword, you can't associate an ID with a POPUP and you can't easily climb up the menu tree programmatically. You have to recursively search down for a menu item, and keep track of the parents.

I wrote the following code to do this. EnableSubmenuItem works like EnableMenuItem to enable or disable a menu item by ID. It then checks the item's parent menu. If all items in its parent menu are disabled, the parent gets disabled. Conversely if any subitem is enabled, the parent gets enabled.

(BTW the original question, how to find parent hMenu of a menu item, is addressed by routine FindParentMenu, in the following code).

////////////////////////////////////////////////////////////////////////////////
// MenuContainsID - return TRUE if menu hMenu contains an item with specified ID
////////////////////////////////////////////////////////////////////////////////

static BOOL MenuContainsID (HMENU hMenu, UINT id)
{
    int pos;                                          // use signed int so we can count down and detect passing 0
    MENUITEMINFO mf;

    ZeroMemory(&mf, sizeof(mf));                      // request just item ID
    mf.cbSize = sizeof(mf);
    mf.fMask = MIIM_ID;

    for (pos = GetMenuItemCount(hMenu); --pos >= 0; )         // enumerate menu items
        if (GetMenuItemInfo(hMenu, (UINT) pos, TRUE, &mf))    // if we find the ID we are looking for return TRUE
            if (mf.wID == id)
                return TRUE;

    return FALSE;
}

////////////////////////////////////////////////////////////////////////////////
// MenuItemIsSubmenu - returns TRUE if item # pos (position) of menu hMenu is a
// submenu. Sets phSubMenu to menu handle if so
////////////////////////////////////////////////////////////////////////////////

static BOOL MenuItemIsSubmenu (HMENU hMenu, UINT pos, HMENU *phSubMenu)
{
    MENUITEMINFO mf;

    ZeroMemory(&mf, sizeof(mf));                      // request just submenu handle
    mf.cbSize = sizeof(mf);
    mf.fMask = MIIM_SUBMENU;

    if (! GetMenuItemInfo(hMenu, pos, TRUE, &mf))     // failed to get item?
        return FALSE;

    *phSubMenu = mf.hSubMenu;                         // pass back by side effect
    return (mf.hSubMenu != NULL);                     // it's a submenu if handle is not NULL
}

////////////////////////////////////////////////////////////////////////////////
// MenuItemIsEnabled - returns true if item # pos (position) of menu hMenu is
// enabled (that is, is not disabled or grayed)
////////////////////////////////////////////////////////////////////////////////

static BOOL MenuItemIsEnabled (HMENU hMenu, UINT pos)
{
    return ! (GetMenuState(hMenu, pos, MF_BYPOSITION) & (MF_GRAYED | MF_DISABLED));
}

////////////////////////////////////////////////////////////////////////////////
// FindParentMenu - returns handle of the submenu of menu bar hMenu that contains
// an item with id "id". Position of this submenu is passed by side effect to
// pParentPos, and /its/ parent menu is passed by side effect through phGrandparentMenu.
////////////////////////////////////////////////////////////////////////////////

static HMENU FindParentMenu (HMENU hMenu, UINT id, HMENU *phGrandparentMenu, UINT *pparentPos)
{
    int pos;                                          // use signed int so we can count down and detect passing 0
    HMENU hSubMenu, hx;

    for (pos = GetMenuItemCount(hMenu); --pos >= 0; ) {
        if (MenuItemIsSubmenu(hMenu, (UINT) pos, &hSubMenu)) {
            if (MenuContainsID(hSubMenu, id)) {
                *phGrandparentMenu = hMenu;           // set grandparent info by side effect
                *pparentPos = (UINT) pos;
                return hSubMenu;                      // we found the item directly
            }

            if ((hx = FindParentMenu(hSubMenu, id, phGrandparentMenu, pparentPos)) != NULL)
                return hx;                            // we found the item recursively (in a sub-sub menu). It set grandparent info
        }
    }

    return NULL;
}

////////////////////////////////////////////////////////////////////////////////
// AllSubitemsAreDisabled - returns TRUE if all items in a submenu are disabled
////////////////////////////////////////////////////////////////////////////////

static BOOL AllSubitemsAreDisabled (HMENU hMenu)
{
    int pos;                                          // use signed int so we can count down and detect passing 0

    for (pos = GetMenuItemCount(hMenu); --pos >= 0; )
        if (MenuItemIsEnabled(hMenu, (UINT) pos))
            return FALSE;                             // finding one enabled item is enough to stop

    return TRUE;
}

////////////////////////////////////////////////////////////////////////////////
// EnableSubMenuItem - like EnableMenuItem, enables or disables a menu item
// by ID (only; not position!) where hMenu is top level menu.
// Added bonus: If the item is in a pop-up menu, and all items in the popup are
// now disabled, we disable the popup menu itself.
//
// Example:
//        EnableSubMenuItem(hMainMenu, IDM_CONFIGURE, MF_GRAYED);
//
////////////////////////////////////////////////////////////////////////////////

void EnableSubMenuItem (HMENU hMenu, UINT id, UINT enable)
{
    HMENU hParentMenu, hGrandparentMenu;
    UINT parentPos;

    // EnableMenuItem does its job recursively and takes care of the item
    EnableMenuItem(hMenu, id, enable | MF_BYPOSITION);

    // But popup menus don't have IDs so we have find the parent popup menu, and its parent (the
    // grandparent menu), so we can enable or disable the popup entry by position

    if ((hParentMenu = FindParentMenu(hMenu, id, &hGrandparentMenu, &parentPos)) != NULL) 
        EnableMenuItem(hGrandparentMenu, parentPos,
           MF_BYPOSITION | (AllSubitemsAreDisabled(hParentMenu) ? MF_GRAYED : MF_ENABLED));
}
like image 45
B. Knittel Avatar answered Nov 17 '22 06:11

B. Knittel