Click here to Skip to main content
15,887,936 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
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) // no handler defined
    {
        if ( /* nID is one of my special dynamic menu IDs */ )
        {
            if (nCode == CN_COMMAND)
            {
                // Handle WM_COMMAND message
                DoWhateverIWantToDoWhenCommandIsCalled(nID);
            }
            else if (nCode == CN_UPDATE_COMMAND_UI)
            {
                // Update UI element state
                pExtra->Enable(TRUE) ;
                
                if ( /* nID is an element that I want to replace with a popup menu */ )
                {
                    // delete the old menu item. Make sure you keep the position
		    pExtra->m_pMenu->DeleteMenu( pExtra->m_nIndex, MF_BYPOSITION ) ;
		
		    // create a popup menu in its stead
		    CMenu MyPopupSubMenu ;
		    MyPopupSubMenu.CreateMenu() ;
			
		    // creating three menu entries, for argument's sake
		    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) ) ;
			
		    // insert the new popup menu at the exact same position where we deleted the original item from
		    pExtra->m_pMenu->InsertMenu( pExtra->m_nIndex, MF_POPUP | MF_BYPOSITION, MyPopupSubMenu.GetSafeHmenu(), GetPopupMenuTitle( nID ) ) ;

		    // keep the menu when the CMenu object goes out of scope
		    MyPopupSubMenu.Detach() ;
		    
		    // and here is where it breaks in second-level menus:
		    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;   // we processed the command  
    }

    // If we arrive here, we didn't process the command. Call the base class version,
    // so the message map will handle the message
    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?

1 solution

For the popup menu that you create dynamically, try CreatePopupMenu instead of CreateMenu.
 
Share this answer
 

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