动态级联弹出菜单-不可能:-( [英] Dynamic Cascading Popup Menus -- impossible :-(

查看:117
本文介绍了动态级联弹出菜单-不可能:-(的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

试图在MFC中进行级联动态弹出菜单.

不可能.

请注意,在MSDN上有一个动态菜单示例.但是,所有要做的就是从菜单中动态删除给定的菜单项.如果要根据您的
级联弹出菜单的层次结构怎么办 (树结构的)数据,仅在运行时才在编译时未知,仅在菜单树实际上被扩展时才生成?

它的工作方式如下:

在某个地方,您将出现顶级弹出菜单,调用TrackPopupMenu.
pWndPopupOwner传递给TrackPopupMenu.

如果窗口是框架窗口,则为CFrameWnd,然后为
您可以在该窗口上将bOldAutoMenuEnable设置为FALSE.
这样可以防止窗口不显示的菜单项自动变灰
找到一个处理程序.在这种情况下,您应该正确处理所有菜单项,
否则将出现非灰色,可选但不起作用的菜单项.

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;



好的,那么,您的弹出式所有者窗口的类应该被覆盖
OnCmdMsg,就像这样:

public MyPopupOwnerWindow: public CWindow (or CFrameWnd)
{
...
    virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,
	AFX_CMDHANDLERINFO* pHandlerInfo);
...
}




...因为OnCmdMsg是在更新或单击弹出菜单项时调用的那个.

OnCmdMsg传递给它一个指向命令处理程序函数的指针.对于动态添加的菜单项,显然,消息映射中没有处理程序功能,因此这些菜单项的处理程序信息将为NULL.

我们需要级联,动态创建的弹出菜单,因此,某些菜单项在扩展菜单时会发现,应该替换为弹出菜单.

我们唯一可以执行此操作的地方是OnCmdMsg,它是在更新菜单项以进行显示时调用的虚拟方法.

调用OnCmdMsg时,此操作是在循环内从CFrameWnd::InitMenuPopup()向下进行几个调用堆栈级别的,该循环按索引遍历菜单项.因此,我们必须确保当我们用弹出菜单替换菜单项时,我们不会围绕菜单项的顺序进行拖曳操作.

因此我们的OnCmdMsg将如下所示:

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);
}



现在,这应该工作.特别是,它应该以递归方式工作:如果nIDSubItem1(动态添加的菜单项ID之一)又必须替换为弹出菜单,则必须在用nID == nIDSubItem1OnCmdMsg时执行此操作. c13>.

但是:即使将插入的菜单点替换为子菜单,也不会在UI中显示.插入确实可以,但是GetSubMenu(pExtra->m_nIndex)
然后返回NULL,就好像没有插入弹出菜单一样,并且在UI中,这些菜单项的标签旁边没有小箭头",也没有弹出我非常费力的菜单已添加.

对于动态创建的弹出菜单,请尝试使用CreatePopupMenu而不是CreateMenu.

="blockquote> blockquote>

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?

解决方案

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


这篇关于动态级联弹出菜单-不可能:-(的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发语言最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆