Click here to Skip to main content
16,004,587 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Shown below is a code with which attempted to get Menu Data, especially menu string, at runtime, unfortunately nothing was returned. Debugging effort showed that the cbSize, fMask and fType data members of the MENUITEMINFO structure that were initally set were set. Absolutely nothing was returned. What is the right approach?



C++
//Get information about the main menu
		MENUITEMINFO MenuInfo{};
		MenuInfo.cbSize = sizeof(MENUITEMINFO);
		MenuInfo.fMask = MIIM_STRING | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
		MenuInfo.fType = MFT_STRING;
		MenuInfo.dwTypeData = NULL;

		GetMenuItemInfo(hMenu, i, TRUE, &MenuInfo);
		std::wstring wstMenuString(MenuInfo.cch + 1, L'\0');
		MenuInfo.cch++;

		MenuInfo.cbSize = sizeof(MENUITEMINFO);
		MenuInfo.fMask = MIIM_STRING | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
		MenuInfo.fType = MFT_STRING;
		MenuInfo.dwTypeData = &wstMenuString[0];
		GetMenuItemInfo(hMenu, i, TRUE, &MenuInfo);


Shown below is a complete version of the code.

C++
void ModifyAppMenu(HWND hWnd)
{
	hOwnerDrawnMenuFont = GetAppFont();

	HMENU hMenu = GetMenu(hWnd);
	int iMenuCount = GetMenuItemCount(hMenu);
	for (int i = 0; i < iMenuCount; i++)
	{
		//Get information about the main menu
		MENUITEMINFO MenuInfo{};
		MenuInfo.cbSize = sizeof(MENUITEMINFO);
		MenuInfo.fMask = MIIM_STRING | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
		MenuInfo.fType = MFT_STRING;
		MenuInfo.dwTypeData = NULL;

		GetMenuItemInfo(hMenu, i, TRUE, &MenuInfo);
		std::wstring wstMenuString(MenuInfo.cch + 1, L'\0');
		MenuInfo.cch++;

		MenuInfo.cbSize = sizeof(MENUITEMINFO);
		MenuInfo.fMask = MIIM_STRING | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
		MenuInfo.fType = MFT_STRING;
		MenuInfo.dwTypeData = &wstMenuString[0];
		GetMenuItemInfo(hMenu, i, TRUE, &MenuInfo);

		//Save gotten information
		vMenuText.push_back(utf8_encode(wstMenuString));//Save this particular menu's text

		unsigned uSubMenuID{ 0 };
		HMENU hSubMenu{ nullptr };
		if (MenuInfo.hSubMenu == NULL)//If ths is not a submenu, then its is a menuitem so save its ID
		{
			uSubMenuID = MenuInfo.wID;//Menutem ID
		}
		else// if it is submenu,then save ts handle
		{
			hSubMenu = MenuInfo.hSubMenu; //Menu handle
		}


		//Set this particular menu's data and new type
		MenuInfo.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
		MenuInfo.fType = MFT_OWNERDRAW;

		MenuInfo.dwItemData = reinterpret_cast<ULONG_PTR>(&vMenuText[static_cast<int>(vMenuText.size()) - 1][0]);
		if (!uSubMenuID)
		{
			MenuInfo.hSubMenu = hSubMenu;
		}
		else
		{
			MenuInfo.hSubMenu = nullptr;
		}
		MenuInfo.wID = uSubMenuID;
		SetMenuItemInfo(hMenu, i, TRUE, &MenuInfo);


		if (hSubMenu != NULL)
		{
			int iSubMenuCount = GetMenuItemCount(hSubMenu);
			for (int j = 0; j < iSubMenuCount; i++)
			{
				//Get information about this Submenu
				MENUITEMINFO SubMenuInfo{};
				SubMenuInfo.cbSize = sizeof(MENUITEMINFO);
				SubMenuInfo.fMask = MIIM_STRING | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
				SubMenuInfo.fType = MFT_STRING;
				SubMenuInfo.dwTypeData = nullptr;

				GetMenuItemInfo(hSubMenu, j, TRUE, &SubMenuInfo);
				std::wstring wstSubMenuString(SubMenuInfo.cch + 1, L'\0');
				MenuInfo.cch++;
				SubMenuInfo.dwTypeData = &wstSubMenuString[0];
				GetMenuItemInfo(hSubMenu, j, TRUE, &SubMenuInfo);

				//Save gotten information
				vMenuText.push_back(utf8_encode(wstSubMenuString));//Save this particular Submenu's text

				unsigned uSubSubMenuID{ 0 };
				HMENU hSubSubMenu{ nullptr };
				if (MenuInfo.hSubMenu == NULL)//If ths is not a submenu, then its is a menuitem so save its ID
				{
					uSubSubMenuID = MenuInfo.wID;//Menutem ID
				}
				else// if it is submenu,then save ts handle
				{
					hSubSubMenu = MenuInfo.hSubMenu; //Menu handle
				}


				//Set this particular Submenu's data and new type
				SubMenuInfo.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
				SubMenuInfo.fType = MFT_OWNERDRAW;
				
				SubMenuInfo.dwItemData = reinterpret_cast<ULONG_PTR>(&vMenuText[static_cast<int>(vMenuText.size()) - 1][0]);
				if (!uSubSubMenuID)
				{
					SubMenuInfo.hSubMenu = hSubSubMenu;
				}
				else
				{
					SubMenuInfo.hSubMenu = nullptr;
				}
				SubMenuInfo.wID = uSubSubMenuID;
				SetMenuItemInfo(hSubMenu, j, TRUE, &SubMenuInfo);


				if (hSubSubMenu != NULL)
				{
					int iSubSubMenuCount = GetMenuItemCount(hSubSubMenu);
					for (int k = 0; k < iSubSubMenuCount; i++)
					{
						//Get information about this SubSubmenu
						MENUITEMINFO SubSubMenuInfo{};
						SubSubMenuInfo.cbSize = sizeof(MENUITEMINFO);
						SubSubMenuInfo.fMask = MIIM_STRING | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
						SubSubMenuInfo.fType = MFT_STRING;
						SubSubMenuInfo.dwTypeData = nullptr;

						GetMenuItemInfo(hSubSubMenu, k, TRUE, &SubSubMenuInfo);
						std::wstring wstSubSubMenuString(SubSubMenuInfo.cch + 1, L'\0');
						MenuInfo.cch++;
						SubSubMenuInfo.dwTypeData = &wstSubSubMenuString[0];
						GetMenuItemInfo(hSubSubMenu, k,TRUE, &SubSubMenuInfo);

						//Save gotten information
						vMenuText.push_back(utf8_encode(wstSubSubMenuString));//Save this particular SubSubmenu's text

						unsigned uSubSubSubMenuID{ 0 };
						HMENU hSubSubSubMenu{ nullptr };
						if (MenuInfo.hSubMenu == NULL)//If ths is not a submenu, then its is a menuitem so save its ID
						{
							uSubSubSubMenuID = MenuInfo.wID;//Menutem ID
						}
						else// if it is submenu,then save ts handle
						{
							hSubSubSubMenu = MenuInfo.hSubMenu; //Menu handle
						}


						//Set this particular SubSubmenu's data and new type
						SubSubMenuInfo.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
						SubSubMenuInfo.fType = MFT_OWNERDRAW;

						SubSubMenuInfo.dwItemData = reinterpret_cast<ULONG_PTR>(&vMenuText[static_cast<int>(vMenuText.size()) - 1][0]);
						if (!uSubSubSubMenuID)
						{
							SubSubMenuInfo.hSubMenu = hSubSubSubMenu;
						}
						else
						{
							SubSubMenuInfo.hSubMenu = nullptr;
						}
						SubSubMenuInfo.wID = uSubSubSubMenuID;
						SetMenuItemInfo(hSubSubMenu, k, TRUE, &SubSubMenuInfo);

					}

				}
			}
		}

	}
}


What I have tried:

I have spent considerable time debugging to figure out what was wrong.
Posted
Updated 9-Jul-24 1:13am
v2
Comments
Richard MacCutchan 9-Jul-24 4:10am    
Are you sure that your hMenu variable was correctly initialised? Something that you could easily verify by using the debugger.
Gbenbam 9-Jul-24 7:15am    
I have added the complete code, do you still think the problem is initialization failure?
Richard MacCutchan 9-Jul-24 11:24am    
What information have you managed to extract using the debugger? Do all system calls return valid information, are all menus and menuitems correctly accessed?

I have successfully used this code to remove the close button from the system menu :
C++
<pre>    CMenu * pSysMenu = GetSystemMenu( false );
    if( pSysMenu )
    {
        UINT count = pSysMenu->GetMenuItemCount();
        for( UINT n = 0; n < count; ++n )
        {
            MENUITEMINFO mii = { 0 };
            mii.cbSize = sizeof( MENUITEMINFO );
            mii.fMask = MIIM_ID;
            if( ! pSysMenu->GetMenuItemInfo( n, & mii, true ) )
            {
                Trace( "GetMenuItemInfo failed for item %d\n", n );
                continue;
            }

            if( mii.wID == SC_CLOSE )
            {
                // remove the close option and following separator

                Trace( "found SC_CLOSE at position %d\n", n );
                pSysMenu->DeleteMenu( n, MF_BYPOSITION );
                pSysMenu->DeleteMenu( n, MF_BYPOSITION );
                break;
            }
        }
    }
I suspect the problem is in the mask. I see no references to wID so I would start by removing MIIM_ID from the mask. I would also add code to check the return from GetMenuItemInfo to see exactly what it returns. I realize that code uses MFC but the structures and their usage are the same as in W32.
 
Share this answer
 
Comments
Gbenbam 9-Jul-24 6:44am    
What was show above is a snippet of a much more longer program, wID was reference in the lower part of the code. Okay, I will add code to see what was returned from GetMenuItemInfo. By the way, I coded in MFC for years, I abandoned it because it won't allow the use of standard C++ data structure. Now I think my next question should be how to get round that.
Rick York 9-Jul-24 10:39am    
I have no idea why you think MFC "won't allow the use of standard C++ data structure." I use plain C structures and STL classes with MFC all the time. I always avoid using MFC's collections and use STL classes instead. There are a few methods that use CStrings but that's only one I have to deal with and it is rare.
Gbenbam 9-Jul-24 18:45pm    
Okay, just to clarify, visual studio did allow the use of STL, but will not allow you to use an STL data structure with an MFC data structure equivalent. I remember facing that issue multiple times until an author of a book I pointed it out. I just couldn't understand why vectors would not work back then? Maybe, my rejoicing is unnecessary, maybe things have remained so. Maybe you did not face the issue because you never really mixed stl with MFC data structures.
Gbenbam 9-Jul-24 18:39pm    
A few checks online shows that it is now possible. Back in the days of VISUAL STUDIO 2008, it was not possible. I am returning to MFC. MFC, here I come!!! Okay now, I remember one more thing that motivated my departure from MFC. Someone on quora.com said nobody uses MFC for new projects again. He pointed out that MFC is being used for only legacy projects and that new projects are now done with QT. Considering the fact that Microsoft has made several attempts in the past to retire MFC, what he said seem believable. While downloading VS 2022, I noticed that by default MFC what not part of the C++ download.That also strengthened that belief. What is your take on that? Do you use MFC for new projects? Do you know developers that use MFC for new projects?
Rick York 10-Jul-24 15:52pm    
No, I know of no developers who use MFC for new projects but that is of absolutely no concern to me. I have MY work to do and they don't help with it. Yes, I DO use MFC for all new projects I am given and all my projects at home. Incidentally you can see some shots from those in my profile. My avatar image comes from one of them. I am very, very productive using MFC so that is what I use and I can think of only one reason not to: I find microsoft's operating systems to be nauseatingly disgusting. The nonsense they have been adding to them lately turns my stomach. I am about ready to retire and I will move myself to Linux when that happens and I will examine my alternatives then.
Do not use MIIM_STRING flag with MFT_STRING fType. MIIM_STRING is a replacement for MFT_STRING. Consequently, if your focus is the menu string, just as it was for me, do not use MIIM_TYPE flag. One of the uses of MIIM_TYPE is to specify MFT_STRING fType.
 
Share this answer
 
You may want to start using "Defensive Programming". For API calls such as

HMENU hMenu = GetMenu(hWnd);


Add a line:

C++
assert(hMenu);


That line in DEBUG build will make your program to emit an Assertion Failure message box. And in RELEASE builds this line will be No Op.

This will save you a ton of time.
 
Share this answer
 
v2
Comments
Gbenbam 11-Jul-24 2:07am    
The issue have with asserts is that they don't allow continuation of code. That's why I use exceptions. I actually have plans to introduce a small exception class to the code so that I can decide whether I want the code to terminate or not.
steveb 12-Jul-24 12:25pm    
You are mistaken. Assertion do not get compiled in the release code. And allowing the assertion to continue in the debug build is a calamity
Gbenbam 12-Jul-24 13:32pm    
OK. Noted. So, when does one use an assertion? I know that if the assertion was caused by user input, the appropriate thing to do is to alert the user of the error and give them an opportunity to correct themselves. While that is not possible with assertions, it is possible with exceptions. My guess is that assertion should be used for programmer possibly generated errors. If we go by that assumption, the number of assertions in codes may be very large. Not withstanding, I will start using them. Thanks for your solution, I appreciate it greatly.
steveb 13-Jul-24 2:42am    
Normally you can use it when an API call can return NULL pointer due to programmer's error, such as passing a nonexistent parameter into the API or many other non obvious typos. Such things can go unnoticed and the main point of defensive programming is the defense against yourself, as it were. You would especially appreciate this feature later on when you need to maintain your program in a later cycles. Editing old code or swapping one DLL for another can easily introduce "technical debt" bugs. And with assert you would catch those bugs on the first run. But imagine the scenario when these things go unnoticed until the software "shipped" to a customer.
Gbenbam 13-Jul-24 7:48am    
In solution 4 below, an assert was used for the return value of GetMenuItemInfo which was not a pointer. Do you consider it an improper use? Except there is a clear understanding of when to use it, the number of asserts can be astronomically large. Perhaps the rule for use of assertions should be this: use it only in connection with non-user input or non-user interaction when there is absolutely no reason for the code to continue executing if the assertion fails.
If I have collected the pieces of information correctly, menu texts should be queried in WM_CREATE. If you take into account the documentation of the MenuItemInfo structure at learn.microsoft.com and get the appropriate memory, this works without any problems in a Win32 program framework created by the wizard. I would rather realize system calls with TCHAR, but it obviously also works with std::wstring. Instead of determining the address with &wstMenuString[0], I would rather use the data() method for this. I have not investigated further what exactly goes wrong with the original source, the following code works with the Win32 framework:
C
case WM_CREATE: {
        HMENU hMenu = GetMenu( hWnd );
        assert(hMenu);
        UINT count = GetMenuItemCount(hMenu);
        for (UINT n = 0; n < count; ++n) {
            MENUITEMINFO MenuInfo{};
            MenuInfo.cbSize = sizeof(MENUITEMINFO);
            MenuInfo.fMask = MIIM_TYPE;
            BOOL rtn = GetMenuItemInfo(hMenu, n, TRUE, &MenuInfo);
            assert(rtn);
            switch (MenuInfo.fType) {
            case MFT_STRING:
                MenuInfo.cch++;
                std::wstring wstMenuString(MenuInfo.cch, 0);
                MenuInfo.dwTypeData = &wstMenuString[0];
                BOOL rtn = GetMenuItemInfo(hMenu, n, TRUE, &MenuInfo);
                assert(rtn);
                break;
            }
        }
    }
    break;
 
Share this answer
 
Comments
Gbenbam 11-Jul-24 2:48am    
The problem with using TCHAR is that you waste memory because you don't know what the initial size should be and you can never resize it. Initially, I used std::unique_ptr with TCHAR that always involved a redundant variable of code. So, I started using std::wstring and use to use the std::wstring::resize() function. Finally, one day, I figured that I can use one of the constructors of std::wstring, so I don't ever use TCHAR again unless I need a persistent TCHAR * . Now that you have pointed out the std::wstring::data() member, I now have absolutely no reason to use TCHAR again. I wonder why std::wstring::data() didn't cross my mind earlier.
merano99 11-Jul-24 4:37am    
The initial size is known exactly with MenuInfo.cch and is also used by you when reserving the wstring. The TCHAR is compatible for both variants (A+W) of the system calls, which is not the case with wstring. Only the fact that the memory is automatically released with wstring speaks in favor of string objects. I hope that the original question has been completely answered by my contribution. If yes, please mark as solution ...
Gbenbam 12-Jul-24 13:38pm    
I was only speaking generally. I had resolved the problem by myself and even shared my solution with the forum. Its solution 2 above. By the way, you should avoid the use of TCHAR A. Read the article in the following sites for reasons: https://utf8everywhere.org/

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900