I have a MFC CMenu with some items. I would like to convert or replace a single menu item with a sub-menu, with additional menu items. Is there an easy way to do that ?
The CMenu class provides the class member CMenu::SetMenuItemInfo to modify an existing menu item by passing it a properly initialized MENUITEMINFO structure.
To replace a menu item with a pop-up menu (sub menu) you have to perform 3 steps.
1. Create a new pop-up menu
You can either create the menu dynamically by calling CMenu::CreatePopupMenu and populate it with CMenu::InsertMenuItem or load an existing pop-up menu from a resource using CMenu::LoadMenu:
CMenu MyMenu;
MyMenu.CreatePopupMenu();
MENUITEMINFO mii = { 0 };
mii.cbSize = sizeof( MENUITEMINFO );
mii.fMask = MIIM_ID | MIIM_STRING;
mii.wID = IDM_MY_MENU_ITEM1; // #define this in your Resource.h file
mii.dwTypeData = _T( "Menu Item 1" );
MyMenu.InsertMenuItem( 0, &mii, TRUE );
2. Initialize a MENUITEMINFO structure
MENUITEMINFO miiNew = { 0 };
miiNew.cbSize = sizeof( MENUITEMINFO );
miiNew.fMask = MIIM_SUBMENU | MIIM_STRING;
miiNew.hSubMenu = MyMenu.Detach(); // Detach() to keep the pop-up menu alive
// when MyMenu goes out of scope
miiNew.dwTypeData = _T( "Some text" );
3. Replace existing menu item
MyMainMenu.SetMenuItemInfo( IDM_ITEM_TO_BE_REPLACED,
&miiNew,
FALSE );
DrawMenuBar( hWnd );
The call to DrawMenuBar is required whenever a window's menu is modified.
Related
I am creating a pop up menu item in win32 . I was able to create a popup menu item and then I added few icons to it. This is how its looking
sameple code
HMENU Controls = CreatePopupMenu();
InsertMenu(Controls, 0 , MF_STRING, 1, L"FirstMenu00");;
static HBITMAP bmpSource = NULL;
bmpSource = (HBITMAP)LoadImage(NULL, L"C:\\Users\\mac\\bit.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
MENUITEMINFO mii{};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_ID | MIIM_BITMAP | MIIM_DATA;
mii.wID = 1;
mii.hbmpItem = bmpSource;// &paHbm[i];
bool st = InsertMenuItem(Controls, 10, TRUE, &mii);
std::string errSTr = GetLastErrorAsString();
TrackPopupMenuEx(Controls, TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_LEFTBUTTON | TPM_HORPOSANIMATION, xPos, yPos, hwnd, NULL);
I am trying to arrange the menu icons in the following fashion
Ie I am trying to create a pattern where first row has 2 icons and the next row has 3 icons .
How can I achieve the same ?
You can use MF_MENUBREAK or MF_MENUBARBREAK flag in AppendMenu function to create a multi-column menu item. It seems that each raw has the same columns.
Here is a sample you can refer to:\
HMENU hmenuPopup = CreatePopupMenu();
// Add the bitmap menu items to the menu.
bool st = AppendMenu(hmenuPopup, MF_BITMAP, uID, (LPCWSTR)paHbm);
if (!st)
{
ErrorExit(L"AddBitmapMenu");
}
st = AppendMenu(hmenuPopup, MF_BITMAP | MF_MENUBARBREAK, uID + 1, (LPCWSTR)LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP2)));
if (!st)
{
ErrorExit(L"AddBitmapMenu");
}
More reference: Menu Item Separators and Line Breaks, Displaying menu items in multiple columns
I am trying to write an application that does not have a title/caption bar (a gross waste of screen real estate). So, I wish to add a close button ("X") to the end of the menu bar. Here is the code that I have tried:
mII.cbSize = sizeof(MENUITEMINFO);
mII.fMask = MIIM_FTYPE | MIIM_BITMAP;
mII.fType = MFT_BITMAP | MFT_RIGHTJUSTIFY;
mII.hbmpItem = HBMMENU_MBAR_CLOSE;
InsertMenuItem(hMenu, NUMMI, TRUE, &mII);
DrawMenuBar(hwnd);
In this case, "NUMMI" is equal to 5, the current number of main-menu items (numbered '0' through '4'). The new close button would be item number '5'.
However, the code seems to do nothing! No button appears on the menu bar. Am I missing something?
Thanks Remy. I eliminated the MFT_BITMAP flag, and now, it works. The MS documentation did not make this clear.
For those who might be reading this, asking the same question, I should note, that the above code only displays the button. Functionality must be added, just like any other button or menu item. A unique identifier is assigned to the wID member of the structure. The command is handled in the WM_COMMAND case, along with the other menu items.
Here is the revised, working code:
mII.cbSize = sizeof(MENUITEMINFO);
mII.fMask = MIIM_FTYPE | MIIM_BITMAP;
mII.fType = MFT_RIGHTJUSTIFY;
mII.wID = ID_MENUBAR_CLOSE;
mII.hbmpItem = HBMMENU_MBAR_CLOSE;
InsertMenuItem(hMenu, NUMMI, TRUE, &mII);
DrawMenuBar(hwnd);
ID_MENUBAR_CLOSE is a macro, defining a unique number that I assigned to this button. The command is the handled in the WM_COMMAND case:
case ID_MENUBAR_CLOSE:
SendMessage(hwnd, WM_CLOSE, 0, 0L); return 0;
return 0;
Voila, a working close button on the menu bar.
I'm trying to change the Text of a MenuItem created with the winAPI. I tried following:
HMENU menu = LoadMenu(_hInstance, MAKEINTRESOURCE(IDR_MENU1)); //getting the Menu
LPWSTR test = L"test";
MENUITEMINFO mii{ sizeof(MENUITEMINFO) };
if (!GetMenuItemInfo(menu, ID_USER_NAME, false, &mii))
{
return ; // not getting an error here
}
mii.fMask = MIIM_TYPE; // tried with MIIM_TYPE and MIIM_STRING
mii.fType = MFT_STRING;
mii.dwTypeData = test;
if (!SetMenuItemInfo(menu, ID_USER_NAME, false, &mii))
{
return; // no error here either
}
DrawMenuBar(_hWnd);
But it doesn't work it's is not giving an error either, so I guess I just forgot something ?
You will need to get the handle to the current menu displayed on the window and modify that, instead of loading a menu using LoadMenu().
I have a popup menu I would like to modify before it is being displayed. I can actually modify the string of a menu item fine. The problem is, that this renders it useless as nothing happens when the modified menu item is clicked on.
CMenu* pPopup = menu.GetSubMenu(0);
ASSERT(pPopup != NULL);
CWnd* pWndPopupOwner = this;
while(pWndPopupOwner->GetStyle() & WS_CHILD)
pWndPopupOwner = pWndPopupOwner->GetParent();
// modify string
pPopup->ModifyMenu(1, MF_BYPOSITION | MF_STRING, NULL, oss.str().c_str());
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, pWndPopupOwner);
Your call to ModifyMenu is setting the menu's ID to zero (via the third argument). You need to keep that ID the same.
If that's awkward, the SetMenuItemInfo API lets you change the string without changing the ID.
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?
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.
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));
}