Как найти пункт меню (если есть), который открывает данный HMENU при активации? - PullRequest
4 голосов
/ 24 августа 2009

Я бы хотел реализовать функцию с прототипом

/* 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 );

Учитывая дескриптор HMENU, я бы хотел узнать, какой пункт меню (если есть) в приложении открывал его. По сути это обратная функция GetSubMenu .

Мой текущий подход заключается в том, чтобы заглянуть в каждый HMENU окон верхнего уровня приложения и проверить, могу ли я найти пункт меню, который откроет данное подменю при активации. Я делаю это рекурсивно, используя GetMenuItemCount / GetSubMenu .

Это довольно неэффективно, и не работает для меню, которые открываются элементами контекстного меню. Отсюда мне интересно:

У кого-нибудь есть хорошая идея, как найти пункт меню (если есть), который открывает данный HMENU при активации?

ОБНОВЛЕНИЕ: Идея, которая только что пришла мне в голову; должна быть возможность (с помощью функции SetWindowsHookEx ) установить хук, который получает уведомление обо всех входных событиях, произошедших в меню. Всякий раз, когда обнаруживается активация пункта меню, запоминают пункт меню (идентифицируемый парой (HMENU, int)) и HMENU, которые будут открыты элементом меню в глобальной карте. Приведенная выше функция getParentMenuItem может просто выполнить поиск по карте.

ОБНОВЛЕНИЕ до обновления: Идея перехвата, описанная в вышеприведенном обновлении, не будет работать, поскольку она, конечно, будет распознавать только пункт меню -> ассоциации меню для элементов, которые были активированы в некоторых точка.

Хотя это немного уродливо, так как требует от меня держать много состояний (карту); Есть ли более легкие возможности?

Ответы [ 2 ]

2 голосов
/ 08 сентября 2016

Я только что нашел такую ​​же потребность. У меня есть определенные меню в файле .rc, и я хочу выделить серый доступ к всплывающему меню, если все его подпункты будут недоступны. (Можно утверждать, что это препятствует открытию, но в данном конкретном случае это то, что нам нужно).

Как упоминали предыдущие респонденты, если вы создаете меню программным способом, вы можете сохранить дескриптор родительского меню элемента в качестве вспомогательной информации.

Но для меню, определенного в файле ресурсов с использованием ключевого слова POPUP, вы не можете связать идентификатор с POPUP и не можете легко подняться на дерево меню программным путем. Вы должны рекурсивно искать вниз пункт меню и отслеживать родителей.

Я написал следующий код для этого. EnableSubmenuItem работает как EnableMenuItem, чтобы включить или отключить пункт меню по идентификатору. Затем он проверяет родительское меню элемента. Если все элементы в его родительском меню отключены, родительский элемент отключается. И наоборот, если какой-либо подпункт включен, родительский элемент включается.

(Кстати, первоначальный вопрос о том, как найти родительское hMenu элемента меню, решается с помощью процедуры FindParentMenu, в следующем коде).

////////////////////////////////////////////////////////////////////////////////
// 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));
}
2 голосов
/ 24 августа 2009

Вы можете попробовать установить MENUINFO.dwMenuData в качестве дескриптора родительского меню для всех меню, которые вы создаете в своем приложении:

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);

Тогда вам нужно только запросить это dwMenuData поле в вашей функции:

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;
}

Редактировать: Если вы не можете контролировать, как создаются все меню, вы можете использовать ловушку WH_CALLWNDPROC для перехвата при первом создании меню. Хорошая статья (с исходным кодом) описывает, как это можно сделать - вы можете посмотреть на попытку вставить родительский HMENU в созданное меню, используя метод, описанный выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...