如何找到激活后打开给定HMENU的菜单项(如果有)?

我想用原型实现一个函数

/* 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函数可以简单地执行对地图的查找。

对更新的更新:上面更新中描述的挂钩概念将不起作用,因为它当然只能识别菜单项 – >菜单关联,用于在某些时候激活的项目。

这感觉有点难看,因为它让我保持很多状态(地图); 有没有更容易的可能性?

您可以尝试将MENUINFO.dwMenuData设置为您在应用程序中创建的所有菜单的父菜单句柄:

 MENUINFO mi; mi.cbSize = sizeof(MENUINFO); mi.dwMenuData = (ULONG_PTR) 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注入创建的菜单中。

我刚刚发现了同样的需求。 我在.rc文件中定义了菜单,如果所有子项都变灰,我想要访问弹出菜单。 (你可以说这会抑制发现,但是,在这种特殊情况下,它就是我们所需要的)。

正如之前的响应者所提到的,如果您以编程方式创建菜单,则可以将项目的父菜单句柄存储为辅助信息。

但是对于使用POPUP关键字在资源文件中定义的菜单,您无法将ID与POPUP关联,也无法以编程方式轻松爬上菜单树。 您必须递归搜索菜单项,并跟踪父项。

我编写了以下代码来执行此操作。 EnableSubmenuItem的工作方式类似于EnableMenuItem,以按ID启用或禁用菜单项。 然后它检查项目的父菜单。 如果禁用其父菜单中的所有项目,则父项将被禁用。 相反,如果启用了任何子项,则父项将启用。

(BTW原始问题,如何查找菜单项的父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)); }