Previously – Fun with menus – Part I
A few days back I set about trying to get radio items into menus. This is helpful if you want only one of the items in the menu to be active at a time, say, for e.g. a "Select Level" popup menu which has "Level 1", "Level 2" and "Level 3" subitems and only one of them needs to be shown as active or currently selected. You get the idea. No? Here, this pic should help,
A bit of searching led me to the CheckMenuRadioItem() API, and I thought, this is simple. I just need to get the handle to the popup menu, and pass the identifier of the subitem I want selected. And done. Turns out ‘get the handle to the popup menu‘ part isn’t very straight forward.
For future references in this post, here is the menu bar and the popup menu that I used in the resource script (refer pic above)
the menubar,
IDR_MENU SHMENUBAR DISCARDABLE
BEGIN
IDR_MENU_POPUP,
2,
I_IMAGENONE, IDM_OK, TBSTATE_ENABLED, TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE, IDS_OK, 0, NOMENU,
I_IMAGENONE, IDR_MENU_POPUP, TBSTATE_ENABLED, TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE,
IDS_MENU, 0, 0,
END
the popup menu,
IDR_MENU_POPUP MENU DISCARDABLE
BEGIN
POPUP "Menu"
BEGIN
MENUITEM "Toolbox..", IDM_MENU_TOOLBOX
POPUP "Speed"
BEGIN
MENUITEM "8x", IDM_MENU_8X
MENUITEM "16x", IDM_MENU_16X
MENUITEM "32x", IDM_MENU_32X
END
MENUITEM "Settings..", IDM_MENU_SETTINGS
MENUITEM SEPARATOR
MENUITEM "Exit", IDM_MENU_EXIT
END
END
What I did wrong
I looked up the functions available with menus and it seemed easy, LoadMenu(), GetSubMenu() and CheckMenuRadioItem() should do the trick, so here is the handler for the menuitem ‘8x‘, under WM_COMMAND; the handler tries to set ‘8x‘ as the current selection,
case IDM_MENU_8X:
{
HMENU hMenu = NULL, hSubMenu = NULL, hSubSubMenu = NULL;
//handle to the menu bar
hMenu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_MENU_POPUP));
if (hMenu)
{
TCHAR szText[128] = TEXT("");
int i = -1;
//handle to the menu "Menu"
hSubMenu = GetSubMenu(hMenu, 0);
MENUITEMINFO mii;
ZeroMemory(&mii, sizeof(mii));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_TYPE;
mii.fType = MFT_STRING;
mii.dwTypeData = szText;
mii.cch = 127;
i = GetMenuItemInfo(hSubMenu, 1, TRUE, &mii); // mii.dwTypeData = "Speed"
ZeroMemory(&mii, sizeof(mii));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_TYPE;
mii.fType = MFT_STRING;
mii.dwTypeData = szText;
mii.cch = 127;
//handle to the menu "Speed"
hSubSubMenu = GetSubMenu(hSubMenu, 1);
i = GetMenuItemInfo(hSubSubMenu, 1, TRUE, &mii); //mii.dwTypeData = "16X"
i = CheckMenuRadioItem(hSubSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_8X, MF_BYCOMMAND);
}
}
break;
GetMenuItemInfo() api and MENUITEMINFO structure were used while debugging, just to make sure that the menu handles that I was getting were the right ones. All API calls LoadMenu(), GetSubMenu() and CheckMenuRadioItem() were successful, from the return values at least. There was one problem however, the radio item ‘dot’ never showed up. Then I tried to experiment a bit with getting menu handles by position, the result still the same. When I ran out of tricks to pull, I tried to set the radio menu using SetMenuItemInfo(), and a few random variations of GetSubMenu() by menu identifier and by position etc. Darn thing never showed up! It all meant one thing only, there must be something wrong with the menu handles themselves, even though they were valid.
The right way
So next I looked up the samples that ship with the SDK and found a CECamera sample that made use of radio menu items. From what I could tell, it looked like LoadMenu() isn’t the right way to get a handle to the main popup menu. Under WM_CREATE message when you are setting up the application and creating the menu bar using SHCreateMenuBar(), you also do the following,
if (!SHCreateMenuBar(&mbi))
{
g_hWndMenuBar = NULL;
}
else
{
g_hWndMenuBar = mbi.hwndMB;
//get a handle to the main popup menu
g_hMainMenu = (HMENU)SendMessage(mbi.hwndMB, SHCMBM_GETSUBMENU, 0, IDR_MENU_POPUP);
//set the default selection to 8x
hSubMenu = GetSubMenu(g_hMainMenu, 1);
CheckMenuRadioItem(hSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_8X, MF_BYCOMMAND);
}
So you send a message to the main menubar window, asking for the popup menu handle using SHCMBM_GETSUBMENU. g_hMainMenu and hSubMenu are both HMENU types. When the application showed up ‘8x‘ was selected by default. The handlers for 16x and 32x set themselves as the current selection, so under WM_COMMAND we have,
case IDM_MENU_8X:
hSubMenu = GetSubMenu(g_hMainMenu, 1);
CheckMenuRadioItem(hSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_8X, MF_BYCOMMAND);
break;
case IDM_MENU_16X:
hSubMenu = GetSubMenu(g_hMainMenu, 1);
CheckMenuRadioItem(hSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_16X, MF_BYCOMMAND);
break;
case IDM_MENU_32X:
hSubMenu = GetSubMenu(g_hMainMenu, 1);
CheckMenuRadioItem(hSubMenu, IDM_MENU_8X, IDM_MENU_32X, IDM_MENU_32X, MF_BYCOMMAND);
break;
It worked. Video follows,