Tried to make cascading dynamic popup menus in MFC.
Impossible.
Mind you, there is a dynamic menu example
on MSDN. But all that does is dynamically deleting given menu items from a menu. What if you want a cascading hierarchy of popup menus depending on your
(tree-structured) data, not known at compile time, only at runtime, generated only as the menu tree is actually expanded?
Here is how it should work:
Somewhere, you have the top-level popup menu coming up, calling
TrackPopupMenu
.
To that
TrackPopupMenu
, you pass a window,
pWndPopupOwner
.
If the window is a frame window, a
CFrameWnd
, then
you can set
bOldAutoMenuEnable
on that window to FALSE.
That will prevent automatic graying out of menu items for which the window doesn't
find a handler. In that case, you should properly handle all your menu items,
or there will be non-grayed-out, selectable, but non-functional menu items.
BOOL bOldAutoMenuEnable = ((CFrameWnd*)pWndPopupOwner)->m_bAutoMenuEnable;
((CFrameWnd*)pWndPopupOwner)->m_bAutoMenuEnable = FALSE;
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
point.x,
point.y,
pWndPopupOwner);
((CFrameWnd*)pWndPopupOwner)->m_bAutoMenuEnable = bOldAutoMenuEnable;
ok, so then, the class of your popup owner window should have overridden
OnCmdMsg
, like so:
public MyPopupOwnerWindow: public CWindow (or CFrameWnd)
{
...
virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo);
...
}
... because OnCmdMsg is the one that's called when a popup menu item is either being updated or is being clicked.
OnCmdMsg
gets passed to it a pointer to a command handler function. For dynamically added menu items, there are no handler functions in the message map, obviously, so the handler info will be
NULL
for those menu items.
We want cascading, dynamically created popup menus, so there are certain menu items which, as we find out at the time of expanding the menu, should instead be replaced with a popup menu.
The only place where we can do this is in OnCmdMsg, the virtual method that gets called when the menu item is updated for dislplay.
When
OnCmdMsg
is called, this is done a few call stack levels down from
CFrameWnd::InitMenuPopup()
inside a loop that iterates through the menu items by index. Therefore we have to make sure that when we replace menu items with popup menus, we won't shuffle around the sequence of menu items.
So our OnCmdMsg will look like this:
BOOL MyPopupOwnerWindow::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (pHandlerInfo == NULL)
{
if ( )
{
if (nCode == CN_COMMAND)
{
DoWhateverIWantToDoWhenCommandIsCalled(nID);
}
else if (nCode == CN_UPDATE_COMMAND_UI)
{
pExtra->Enable(TRUE) ;
if ( )
{
pExtra->m_pMenu->DeleteMenu( pExtra->m_nIndex, MF_BYPOSITION ) ;
CMenu MyPopupSubMenu ;
MyPopupSubMenu.CreateMenu() ;
int nIDSubItem1 = GetOneOfMyOwnNewUniqueMenuIDs() ;
MyPopupSubMenu.AppendMenu( MF_STRING, nIDSubItem1, GetNewSubMenuItemTitle(nID,nIDSubItem1) ) ;
int nIDSubItem2 = GetOneOfMyOwnNewUniqueMenuIDs() ;
MyPopupSubMenu.AppendMenu( MF_STRING, nIDSubItem2, GetNewSubMenuItemTitle(nID,nIDSubItem2) ) ;
int nIDSubItem3 = GetOneOfMyOwnNewUniqueMenuIDs() ;
MyPopupSubMenu.AppendMenu( MF_STRING, nIDSubItem3, GetNewSubMenuItemTitle(nID,nIDSubItem3) ) ;
pExtra->m_pMenu->InsertMenu( pExtra->m_nIndex, MF_POPUP | MF_BYPOSITION, MyPopupSubMenu.GetSafeHmenu(), GetPopupMenuTitle( nID ) ) ;
MyPopupSubMenu.Detach() ;
CMenu *pMenu = pExtra->m_pMenu->GetSubmenu( pExtra->m_nIndex ) ;
if ( NULL == pMenu )
{
TRACE(_T("Added submenu not retrieved at position %d\n"), pExtra->m_nIndex ) ;
}
}
}
}
return TRUE;
}
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
Now, this should work. In particular, it should work recursively: If
nIDSubItem1
, one of the menu item IDs added dynamically, in turn must be replaced by a popup menu, this would have to be done when
OnCmdMsg
is called with
nID == nIDSubItem1
and
nCode == CN_UPDATE_COMMAND_UI
.
But: Even though the inserted menu point is replaced by a submenu, this is not shown in the UI. Insert does work, but
GetSubMenu(pExtra->m_nIndex)
afterwards returns
NULL
, as if no popup menu was inserted, and in the UI, those menu items don't have a little "arrow" next to their labels and don't pop up the menu that I have so painstakingly added.
So does anyone have a better idea how to do this?