1 Attachment(s)
Problem with owner draw menu's and appendMenu
See the attachment for it.
What I did is I loaded a menu from a resource. After that I have to modify a couple of menuitems in some submenu's.
My code looks like this:
Code:
CNewMenu* pThirdMenu = (CNewMenu *)m_NewMenu.GetSubMenu(2);
pThirdMenu->AppendMenu(MF_STRING, someValidUINT, someValidText);//a couple of times like this
::SendMessage(m_wndMenuBar.m_hWndMDIClient,WM_MDISETMENU,(WPARAM)m_NewMenu.m_hMenu,NULL);//to reload the CMenuBar with the newly formed CNewMenu.
The last line of code, I am not sure of, because GuiToolKit lacks documentation. Did someone used this before this way? I am using the GuiToolKit ownerdraw menu's. This works with a CMenuBar and CNewMenu.
I can click the menubar's buttons (the ones with the ugly text on it) and the submenu shows up nicely containing the items I added with appendmenu.
Did someone see this before or/and knows how to do it?
timv
ps: I also posted this on http://www.codeproject.com/library/guitoolkit.asp but as I said there is no support (nor documentation)
Re: Problem with owner draw menu's and appendMenu
Does someone has a clue about this?
I am really stuck at this problem...
Re: Problem with owner draw menu's and appendMenu
I found out that after modifying the menu and doing
Code:
::SendMessage(m_wndMenuBar.m_hWndMDIClient,WM_MDISETMENU,(WPARAM)m_NewMenu.m_hMenu,NULL);//to reload the CMenuBar with the newly formed CNewMenu.
the menubuttons are to be initialized again. Then for every button it's text is looked up according to the position of the button. Like this:
Code:
::GetMenuItemInfo(hMenu, nIndex, TRUE, &info);
but this fails? GetLastError returns 0, but the text is not found, leaving my TCHAR szText[256]; uninitialized.
I don't understand why this fails. The hMenu seems to be allright. GetMenuItemCount(hMenu) works fine because it returns 14 (the number of menuitems I have).
What an awkward problem.
Re: Problem with owner draw menu's and appendMenu
One issue could be down to you trying to use a opup type menu as a top level menu.
When a menu is used in a mainframe/dialog popup menus in th etop layer appear like:
File Edit Window Help
Such menus as these are created using CreateMenu. If you use a menu created using CreatePopupMenu, the menu will not show corretly.
As you are getting a sub-menu from a loaded menu resource and setting this as the top level for you window, it will have probably been created using CreatePopupMenu() during the resource loading stage.
What I suggest you do is that you create the new menu from scratch or extract you sub-menu from the menu resource and load that directly instead.
Re: Problem with owner draw menu's and appendMenu
Quote:
Originally Posted by Roger Allen
As you are getting a sub-menu from a loaded menu resource and setting this as the top level for you window, it will have probably been created using CreatePopupMenu() during the resource loading stage.
I don't think this is the issue. I am not setting a submenu as toplevel menu. The menu that I'm setting is a toplevel menu (m_NewMenu). The issue here is probably the use of an owner draw menu and a CMenuBar. The m_NewMenu is alright (I can see the right menuitems added to the submenu's), but something goes wrong when trying to do CMenuBar.SetMenu(m_NewMenu) which makes GetMenuItemInfo() on the newly setted menu's handle fail.
Quote:
Originally Posted by Roger Allen
What I suggest you do is that you create the new menu from scratch...
Some time ago I tried to make the menu from scratch. I got the same problem plus some other issues.
Quote:
Originally Posted by Roger Allen
...or extract you sub-menu from the menu resource and load that directly instead.
I don't understand what you mean by this. Isn't this what I did by my code:
Code:
CNewMenu* pThirdMenu = (CNewMenu *)m_NewMenu.GetSubMenu(2);//m_NewMenu is the loplevel menu
pThirdMenu->AppendMenu(MF_STRING, someValidUINT, someValidText);//a couple of times like this
::SendMessage(m_wndMenuBar.m_hWndMDIClient,WM_MDISETMENU,(WPARAM)m_NewMenu.m_hMenu,NULL);//to reload the CMenuBar with the newly formed CNewMenu.
Unfortunately the guitoolkit isn't documented and it seems noone has some experience with it's ownerdrawn menu and modifying such a menu.
Re: Problem with owner draw menu's and appendMenu
Ok, I htink i know what your doing now.
When you update a menu which is selected (or in use), you need to call DrawMenuBar() on the window which is using it after making any changes to the menu.
The other possible problem is that ownerdrawn menu code has to set the MF_OWNERDRAW flag on the menu item otherwise you do not recieive the WM_MEASURE item and WM_DRAWITEM messages for that item.
If this is the case in you menu, all new items would appear as they would if you did not have OD menus setup.
Also, the OD menu code may be storing information about the menu item in the user data for that menu item. I would check the CNewMenu code to see what it does to make the menu ownerdrawn and how it interacts with each item when doing so.
I did write an OD menu class that allows the user to keep using CMenu in their application, so it cold be very different from what you are currently using.
It can be found here http://www.codeproject.com/menu/QuickODmenu.asp, and may be of use as a reference or a replacement
Re: Problem with owner draw menu's and appendMenu
Thanks alot for your reply Roger.
The DrawMenuBar() I tried before and didn't do the trick. Same result with that.
The MF_OWNERDRAW is done automatically in the CNewMenu::AppendMenu() function. I tried to add it to my AppendMenu calls, but still the same result.
After doing these lines
Code:
CNewMenu* pThirdMenu = (CNewMenu *)m_NewMenu.GetSubMenu(2);
pThirdMenu->AppendMenu(MF_STRING, someValidUINT, someValidText);//a couple of times like this
::SendMessage(m_wndMenuBar.m_hWndMDIClient,WM_MDISETMENU,(WPARAM)m_NewMenu.m_hMenu,NULL);//to reload the CMenuBar with the newly formed CNewMenu.
the following code is executed by the WM_MDISETMENU handler: (in method InitItems())
Code:
for (int i = 0; i < ::GetMenuItemCount(m_hMenu); ++i) {
m_arrItem.Add(new CMenuButton(m_hMenu, i,this));
}
GetMenuItemCount recognizes here that my m_hMenu has 14 menuitems at top level.
The CMenuButton constructor calls InitButtonStringAndSubMenuHandle():
Code:
void CMenuButton::InitButtonStringAndSubMenuHandle(HMENU hMenu, int nIndex)
{
// get menu button Text
TCHAR szText[256];
MENUITEMINFO info; ::memset(&info, 0, sizeof(MENUITEMINFO));
info.cbSize = sizeof(MENUITEMINFO);
info.fMask = MIIM_ID | MIIM_TYPE;
info.dwTypeData = szText;
info.cch = sizeof(szText);
::GetMenuItemInfo(hMenu, nIndex, TRUE, &info);
m_strBtn = CString(szText);
m_bt.SetCaption(m_strBtn);
m_hSubMenu = ::GetSubMenu(hMenu, nIndex);
if (!m_hSubMenu) {
m_nID = ::GetMenuItemID(hMenu, nIndex);
ASSERT(m_nID != -1);
}
else {
m_nID = -1;
}
}
And THIS is what actually goes wrong: The ::GetMenuItemInfo does not give a error. It finds some MENUITEMINFO wID (the id of the button, or some handle of a popupmenu in case of a submenu), but it doesn't find the dwTypeData which is the text. So it sets m_strBtn, the text on the button, to szText, which is uninitialized. That's why the effect in the screenshot occurs. But how comes the GetMenuItemInfo() function doesn't do his job properly?? What possible reasons could there be for this?
I must have forgotten something about it..
Re: Problem with owner draw menu's and appendMenu
Two things:
1. I think you need to specify the MIIM_STRING type flag to correctly get the menu items text (you may also need to setup the MENUITEMINFO object with a buffer to receive this text. Not sure MIIM_TYPE flag should be used at all.
2. szText which is being used in your example code should be initialised to ""
If you take a look at my ownerdrawn menu article which I referenced in an earlier reply, you should see many calls to ::GetMenuItemInfo() there which are used in very similar ways to what you are trying to do.
Re: Problem with owner draw menu's and appendMenu
Hi,
here I am again.
The problem is still not solved. I tried the 4 possible combinations of MIIM_STRING and MIIM_TYPE. But none of them give the result.
I must say that if I just load the menu as a resource without appending menuitems to it, the function InitButtonStringAndSubMenuHandle() I sent in previous post, works. However, when I start doing AppendMenu() the strings of the upper level menuitems, can't be fetched. So I don't think it's a problem with the MIIM_TYPE or another flag. So my guess is the problem is not in this function or in the GetMenuItemInfo() function, but there's probably something wrong with the HMENU in some way... :-( I don't see why this is the case.
(Also, I cannot set szText to "", because the guitoolkit asserts on menuitems containing an empty text.)
Thx for helping me anyway. I hope you (or someone else) still have a suggestion...
greets,
timv
The full code where I modify the menu
Here is the full code where I modify the menu. Perhaps something is wrong in it? However the m_NewMenu is modified perfectly at the end of this function. I think it goes wrong when setting the m_wndMenuBar to having m_NewMenu as menu (the sendmessage line; this leads to my function InitButtonStringAndSubMenuHandle()). I must also say that the InitButtonStringAndSubMenuHandle() is executed twice: once when the menuresource is loaded and the second time when i reset the menu on the menubar (the sendmessage line).
Code:
long CMainFrame::OnAddToMenu(UINT wParam, long lParam)
{
std::vector<CUserFunction> functions = theApp.m_pUserPrefs->GetUserFunctions();
CNewMenu* pCustomerMenu = (CNewMenu *)m_NewMenu.GetSubMenu(2);
CNewMenu* pSupplierMenu = (CNewMenu *)m_NewMenu.GetSubMenu(3);
CNewMenu* pCommunicationMenu = (CNewMenu *)m_NewMenu.GetSubMenu(6);
CNewMenu* pMenu;
CNewMenu* pModifyMenu = (CNewMenu *)m_NewMenu.GetSubMenu(8)->GetSubMenu(22)->GetSubMenu(0)->GetSubMenu(4);
CUserFunction* pUF;
LPCTSTR text;
CString thename;
const char *cstr;
for (unsigned int s = 0 ; s < functions.size() ; s++)
{
pUF = &functions.at(s);
thename = pUF->GetName();
cstr = thename;
text = (LPCTSTR)cstr;
if (pUF->GetUint() < IDM_DOCGEBRUIK || pUF->GetUint() > IDM_DOCGEBRUIK + MAXDOCUMENTEN)
continue;
if (pUF->GetGroupId() == 2)//supplier
pSupplierMenu->AppendMenu(MF_STRING|MF_OWNERDRAW, pUF->GetUint(), text);
else if (pUF->GetGroupId() == 3)//customer
pCustomerMenu->AppendMenu(MF_STRING|MF_OWNERDRAW, pUF->GetUint(), text);
else if (pUF->GetGroupId() == 6)//communication
pCommunicationMenu->AppendMenu(MF_STRING|MF_OWNERDRAW, pUF->GetUint(), text);
else
continue;
//add a doc
if (theApp.m_nUserGroup == 0)
pModifyMenu->AppendMenu(MF_STRING|MF_OWNERDRAW, pUF->GetUint()-IDM_DOCGEBRUIK+IDM_DOCBEGIN, text);
}
for (unsigned int s = 0 ; s < functions.size() ; s++)
{
pUF = &functions.at(s);
thename = pUF->GetName();
cstr = thename;
text = (LPCTSTR)cstr;
if (pUF->GetUint() < IDM_MODSTART || pUF->GetUint() > IDM_MODSTART + MAXMODULES)
continue;
if (pUF->GetGroupId() == 2)//supplier
pMenu = pSupplierMenu;
else if (pUF->GetGroupId() == 3)//customer
pMenu = pCustomerMenu;
else if (pUF->GetGroupId() == 6)//communication
pMenu = pCommunicationMenu;
else
continue;
::AppendMenu(pMenu->m_hMenu,MF_STRING|MF_OWNERDRAW, pUF->GetUint(), text);
}
::SendMessage(m_wndMenuBar.m_hWndMDIClient,WM_MDISETMENU,(WPARAM)m_NewMenu.m_hMenu,NULL);
DrawMenuBar();
return 0;
}
Oh yeah, at the time this function is called, m_NewMenu is the CNewMenu that has hMenu attached (with hMenu being the handle of the menu loaded using the menu resource).
Re: Problem with owner draw menu's and appendMenu
FIrst thing. When I looked at the CNewMenu source code, it had a built in AppendODmenu() function. Have you tried calling this function instead?
This will propbably handle all the extra bits which may need to be associated with a menu item to make it work correctly.
A different approach to your looking for a specific submenu.
You have:
Code:
CNewMenu* pModifyMenu = (CNewMenu *)m_NewMenu.GetSubMenu(8)->GetSubMenu(22)->GetSubMenu(0)->GetSubMenu(4);
Which is using hard coded submenu values. If you change the structure of your menu, you break your code. It would be better to do it like this:
Code:
CNewMenu menu;
HMENU hMenu = GetSubMenu(m_NewMenu.m_hMenu, L"MenuLayer1Name\\MenuLayer2Name\\..\\ModifyMenu");
if (hMenu)
{
menu.Attach(hMenu);
.. add your new menu items here
}
HMENU GetSubMenu(HMENU hMenu, CString popupMenuToFind)
{
HMENU popupMenuHandle = NULL;
bool bFoundSubMenu = false;
int itemCount = ::GetMenuItemCount(hMenu);
// first, does the menu item have any required submenus to be found?
if (popupMenuToFind.Find('\\') >= 0)
{
// yes, we need to do a recursive call on a submenu handle and with that sub
// menu name removed from popupMenuToFind
// 1:get the popup menu name
CString popupMenuName = popupMenuToFind.Left(popupMenuToFind.Find('\\'));
// 2:get the rest of the menu item name minus the delimiting '\' character
CString remainingText = popupMenuToFind.Right(popupMenuToFind.GetLength() - popupMenuName.GetLength() - 1);
// 3:See whether the popup menu already exists
MENUITEMINFO menuItemInfo;
memset(&menuItemInfo, 0, sizeof(MENUITEMINFO));
menuItemInfo.cbSize = sizeof(MENUITEMINFO);
menuItemInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID | MIIM_SUBMENU;
for (int itemIndex = 0 ; itemIndex < itemCount && !bFoundSubMenu ; itemIndex++)
{
::GetMenuItemInfo(
hMenu,
itemIndex,
TRUE,
&menuItemInfo);
if (menuItemInfo.hSubMenu != 0)
{
// this menu item is a popup menu (non popups give 0)
TCHAR buffer[MAX_PATH];
::GetMenuString(
hMenu,
itemIndex,
buffer,
MAX_PATH,
MF_BYPOSITION);
if (popupMenuName == buffer)
{
// this is the popup menu we have to add to
bFoundSubMenu = true;
}
}
}
// 4: If exists, do recursive call, else create do recursive call and then insert it
if (bFoundSubMenu)
{
popupMenuHandle = GetSubMenu(
menuItemInfo.hSubMenu,
remainingText);
}
}
else
{
MENUITEMINFO menuItemInfo;
memset(&menuItemInfo, 0, sizeof(MENUITEMINFO));
menuItemInfo.cbSize = sizeof(MENUITEMINFO);
menuItemInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID | MIIM_SUBMENU;
for (int itemIndex = 0 ; itemIndex < itemCount && !bFoundSubMenu ; itemIndex++)
{
::GetMenuItemInfo(
hMenu,
itemIndex,
TRUE,
&menuItemInfo);
if (menuItemInfo.hSubMenu != 0)
{
// this menu item is a popup menu (non popups give 0)
TCHAR buffer[MAX_PATH];
::GetMenuString(
hMenu,
itemIndex,
buffer,
MAX_PATH,
MF_BYPOSITION);
if (popupMenuToFind == buffer)
{
popupMenuHandle = menuItemInfo.hSubMenu;
bFoundSubMenu = true;
}
}
}
}
ASSERT(bFoundSubMenu);
return popupMenuHandle;
}
This will allow you to find your popup menus by name, which should be much more robust.
Re: Problem with owner draw menu's and appendMenu
AppendODMenu(), AppendMenu(), ::AppendMenu, they all give the same result. I've tried them all, but the problem is not solved.
Thanks alot for getting so deep into my problem. The problem on itself seems not so hard, and I really need this dynamiccally filled menus. So hopefully someone comes out with something that guides me to a solution.
OT:
Thanks for the tip about the submenus. But my resources can be loaded in different languages, so the popup menus will have a different string as name.
Re: Problem with owner draw menu's and appendMenu
The menu name can still be loaded from a string resource ID.
One other suggestion. Your modifying the menu when its a CNewMenu. Any chance that you can do all the modifications before it gets converted to a CNewMenu from a CMenu?
I once went through this kind of problem using BCMenu. In the end I wrote my own OD menu class (See reference above) which leaves everything as a standard CMenu object and hooks the window that needs to draw the menu.
Its a much cleaner approach compared to all these methods where they attach user item data to each menu item as you have to worry about the destruction of all these dynamically allocated resources.
Re: Problem with owner draw menu's and appendMenu
I just spent some tie looking through the CNewMenu source from the article. It looks like you must use the AppenODMenu() functions. As the actual menu item text is stored in the CNewMenuItemData object and not in the HMENU item. This is probably why the GetMenuItemInfo function is failing as its using the text values from the HMENU which are not set.
When you need to get the text of a menu item, you will need to use a custom function on CNewMenu to retrieve it.
Re: Problem with owner draw menu's and appendMenu
Quote:
Originally Posted by Roger Allen
One other suggestion. Your modifying the menu when its a CNewMenu. Any chance that you can do all the modifications before it gets converted to a CNewMenu from a CMenu?
I tried to modify the menu working with HMENU. But I came to the same result.
Quote:
Originally Posted by Roger Allen
Its a much cleaner approach compared to all these methods where they attach user item data to each menu item as you have to worry about the destruction of all these dynamically allocated resources.
I totally agree about this.
Quote:
Originally Posted by Roger Allen
I just spent some tie looking through the CNewMenu source from the article. It looks like you must use the AppenODMenu() functions. As the actual menu item text is stored in the CNewMenuItemData object and not in the HMENU item. This is probably why the GetMenuItemInfo function is failing as its using the text values from the HMENU which are not set.
All the AppendMenu() methods call AppendODMenu() methods. So even then the hMenu's text values are not set.
I think you are right about the menu item text and the HMENU problems. I think the problem is in the CMenuBar and CMenuButtons. A CMenuBar holds all the CMenuButtons, which are the top level menu items. Those menubuttons hold their strings. When I modify the menu, a function is called (InitItems) which removes the previous buttons and creates new ones. At that time I need those buttons's strings, but I can not get them because GetMenuItemInfo() doesn't work because of the hMenu. And the hMenu is all this CMenuBar knows :s. And
Quote:
Originally Posted by Roger Allen
When you need to get the text of a menu item, you will need to use a custom function on CNewMenu to retrieve it.
The CMenuBar is only connected to a CNewMenu in the way that they both load the same menuresource at init time. So I don't see how to do it.
It seems to me this is not so well implemented in the guitoolkit.