enums and DeleteMenu()
I wanted to add a menu item that had a child menu to one of the menus in the IE menu bar. I always kind of dread adding menu items to IE, because there is a lot of stuff going on there. IE and Explorer share the same frame, and the menu items are different depending on whether the frame is hosting the default shell view ("c:\foo", etc.) or mshtml ("https://www.msn.com", etc). It gets even crazier when you consider we can also host office documents and then we get all their menu commands as well.
Anyway, all I wanted to do was add a little submenu. The first thing I had to do was to create the submenu. All I did was define my base id and an enum for each submenu item. That way I could create an array of bools (not shown) to keep track of which items were on and which items were off and easily access them by id. I inserted each submenu item as ID_MENU_BASE + ID_subitem. It was easy:
...
enum
{
ITEM_FOO,
ITEM_BAR,
ITEM_BAZ,
ITEM_ETC,
ITEM_QUX,
ITEMCOUNT
};
...
HMENU hMenu = CreatePopupMenu();
WCHAR szDisplay[128];
if (hMenu)
{
LoadString(HINST_MYDLL, IDS_FOO, szDisplay, ARRAYSIZE(szDisplay));
AppendMenu(hMenu, MF_STRING | MF_ENABLED, ID_MENU_BASE + ITEM_FOO, szDisplay);
LoadString(HINST_MYDLL, IDS_BAR, szDisplay, ARRAYSIZE(szDisplay));
AppendMenu(hMenu, MF_STRING | MF_ENABLED, ID_MENU_BASE + ITEM_BAR, szDisplay);
// ... etc. Repeat for each item in the enum.
}
...
Then I had to figure out how to insert a menu item that had MF_POPUP set and an identifier set. I had to set the identifier because later on when I want to delete it, I would not necessarily know what position it would be in, since menu items tend to come and go. InsertMenu() reuses the uIDNewItem argument, so if you set MF_POPUP you have to pass the handle to the submenu in the field where you would otherwise set the id. So instead I had to use InsertMenuItem():
...
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_ID | MIIM_STATE | MIIM_SUBMENU | MIIM_TYPE;
mii.fType = MFT_STRING;
mii.fState = MFS_ENABLED;
mii.wID = ID_MENU_BASE;
mii.hSubMenu = hMenu;
mii.dwTypeData = szDisplay;
mii.cch = lstrlen(szDisplay);
InsertMenuItem(hMenuRoot, ID_ITEM_TO_INSERT_BEFORE, FALSE, &mii);
...
And the last thing I had to do was delete everything when it needed to go away. Seemed easy enough:
DeleteMenu(hMenuRoot, ID_MENU_BASE, MF_BYCOMMAND);
And it did not work. I was very sad. Here is why it did not work: I was off by one. In cases where my submenu included ITEM_FOO, ITEM_FOO was appended as ID_MENU_BASE + ITEM_FOO = ID_MENU_BASE + 0 = ID_MENU_BASE !! (Note: + 0 because ITEM_FOO is the first member of my enum.) If the item you ask it to delete has a submenu, DeleteMenu() has to go and delete all submenus of the item and it does so recursively. It looked at my submenu, saw the first item was what I asked it to delete and promptly stopped recursing.
The moral of this story: Do not make submenu items that have the same identifier as any of their parent items.