如何在CTreeCtrl / CWnd中接收ON_UPDATE_COMMAND_UI?

问题描述 投票:0回答:1

我正在开发MFC MDI应用程序。我想有选择地启用或禁用一些右键单击上下文菜单命令,但是在使用ON_UPDATE_COMMAND_UI禁用某些命令来实现功能后,菜单命令仍然保持活动状态。

似乎在适当的时间未触发或处理ON_UPDATE_COMMAND_UI消息。

目标是实现取决于所选树元素的CTreeCtrl上下文菜单

我的应用程序结构

主CWinAppEx启动CMDIFrameWndEx。 CMDIFrameWndEx对象包含普通的MDI子框架,但它还包含一个CDockablePane,它本身包含一个CTreeCtrl,旨在与Visual Studio中的Project或Solution树视图类似地使用。

enter image description here

我在从CTreeCtrl继承的类中实现了ON_UPDATE_COMMAND_UI消息处理程序。 当我单击菜单项本身时,将调用这些处理程序,但是为时已晚;应该在菜单打开之前调用它们。

我怀疑这与命令(或消息)路由有关;环顾四周表明某些控件或窗口类未收到ON_COMMAND_UPDATE_UI消息,因为它们是在CFrameWnd级别处理的。但是,这些讨论中提出的解决方法或解决方案并未明确说明。我想遵守常见的MFC / MDI习惯用法,因此我希望对此问题进行一个较全面的,初学者级的解释。

CDockablePane窗口(或CTreeCtrl控件)是否不打算与ON_COMMAND_UPDATE_UI进行交互?为什么是这样?我还想念其他东西吗?

我的直觉是,无需亲自连接一堆事件就可以实现此目的,因为MFC框架应该将消息传递给可能处理它们的任何类。我开始认为我缺少这种行为的微妙之处。

代码

从我的CTreeCtrl继承类(CLCPViewTree):

BEGIN_MESSAGE_MAP(CLCPViewTree, CTreeCtrl)
    ON_NOTIFY_REFLECT(NM_RCLICK, OnRClick)
    ON_COMMAND(ID_LCPGATEWAYCONTEXTMENU_SAVE_SESSION, SaveSession)
    ON_COMMAND(ID_LCPGATEWAYCONTEXTMENU_LOAD_SESSION, LoadSession)
    ON_COMMAND(ID_LCPGATEWAYCONTEXTMENU_SAVE, SaveDocument)
    ON_COMMAND(ID_LCPGATEWAYCONTEXTMENU_LOAD, LoadDocument)
    ON_UPDATE_COMMAND_UI(ID_LCPGATEWAYCONTEXTMENU_LOAD, OnUpdateMenuLoadSingle)
    ON_UPDATE_COMMAND_UI(ID_LCPGATEWAYCONTEXTMENU_SAVE, OnUpdateMenuLoadSingle)
    ON_UPDATE_COMMAND_UI(ID_LCPGATEWAYCONTEXTMENU_LOAD_SESSION, OnUpdateMenuLoadSession)
    ON_UPDATE_COMMAND_UI(ID_LCPGATEWAYCONTEXTMENU_SAVE_SESSION, OnUpdateMenuSaveSession)
    ON_WM_CONTEXTMENU()
END_MESSAGE_MAP()

afx_msg void CLCPViewTree::OnUpdateMenuLoadSingle(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bShowSingleInstanceMenu);
}

afx_msg void CLCPViewTree::OnUpdateMenuSaveSingle(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bShowSingleInstanceMenu);
}

afx_msg void CLCPViewTree::OnUpdateMenuLoadSession(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bShowSessionMenu);
}

afx_msg void CLCPViewTree::OnUpdateMenuSaveSession(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bShowSessionMenu);
}

void CLCPViewTree::OnRClick(NMHDR* pNMHDR, LRESULT* pResult) 
{
    TRACE0("CLCPViewTree::OnRClick()\r\n");

    HTREEITEM hItem = GetSelectedItem();

    if(!hItem)
    {
        return;
    }

    CString text = GetItemText(hItem);
    TRACE0(text);

    //To get your element:
     SelectorReference* ref = (SelectorReference *) (GetItemData(hItem));

    if(ref == nullptr)
    {
        m_bShowSessionMenu = false;
        m_bShowSingleInstanceMenu = false;
    }
    else if(ref->is_program)
    {
        m_bShowSessionMenu = false;
        m_bShowSingleInstanceMenu = true;

        // Send WM_CONTEXTMENU to self
        CString path = CString(ref->p_pd->ProgramPath) + "sim";
        TRACE0("Controller path:\r\n");
        TRACE0(path + "\r\n");
        SelectedControllerPath = path;
        SelectedLCPGatewayView = ref->view;
        SendMessage(WM_CONTEXTMENU, (WPARAM) m_hWnd, GetMessagePos());
    }
    else
    {
        m_bShowSessionMenu = false;
        m_bShowSingleInstanceMenu = false;
    }

    // Mark message as handled and suppress default handling
    *pResult = 1;
}

void CLCPViewTree::OnContextMenu(CWnd* pWnd, CPoint ptMousePos) 
{
    // if Shift-F10
    if (ptMousePos.x == -1 && ptMousePos.y == -1)
        ptMousePos = (CPoint) GetMessagePos();

    ScreenToClient(&ptMousePos);

    CMenu menu;
    CMenu* pPopup;

    // the font popup is stored in a resource
    menu.LoadMenu(IDR_PROGRAM_MENU);
    pPopup = menu.GetSubMenu(0);
    ClientToScreen(&ptMousePos);
    pPopup->TrackPopupMenu( TPM_LEFTALIGN, ptMousePos.x, ptMousePos.y, this );
}

新代码

此后,我在我的CMDIFrameWndEx继承的类(CMainFrame)和我的CDockablePane继承的类(LCPSelector)中添加了对OnCmdMsg的替代。目的是将命令消息一直传递到CTreeCtrl对象,以防它们可能被处理。

我根据此处对命令路由的讨论添加了此代码:https://docs.microsoft.com/en-us/cpp/mfc/command-routing?view=vs-2019

不过仍然获得相同的结果。也许这是错误的方向,或者我也缺少其他东西。

供参考:CMainFrame继承自CMDIFrameWndEx,而LCPSelector继承自CDockablePane。

BOOL CMainFrame::OnCmdMsg(UINT id,int code , void *pExtra,AFX_CMDHANDLERINFO* pHandler)
{
   //route cmd first to registered dockable pane
    if(m_wndSelector.OnCmdMsg(id,code,pExtra,pHandler))
    {
        return TRUE;
    }

  return CMDIFrameWndEx::OnCmdMsg(id,code,pExtra,pHandler);
}

BOOL LCPSelector::OnCmdMsg(UINT id,int code , void *pExtra,AFX_CMDHANDLERINFO* pHandler)
{
   //route cmd first to registered dockable pane
    if(m_ctrlLCPViewTree.OnCmdMsg(id,code,pExtra,pHandler))
    {
        return TRUE;
    }

  return CDockablePane::OnCmdMsg(id,code,pExtra,pHandler);
}
c++ mfc mdi
1个回答
0
投票

打开菜单时,会生成WM_INITMENUPOPUP消息,它会调用OnInitMenuPopup

[OnInitMenuPopup根据ON_UPDATE_COMMAND_UI更新菜单

如果您在popup->TrackPopupMenu(TPM_LEFTALIGN, x, y, this)类中调用CTreeCtrl,则必须自己处理OnInitMenuPopup。否则,将忽略ON_UPDATE_COMMAND_UI

[在许多情况下,使用AfxGetMainWnd()作为菜单父项的窗口句柄会更容易。这应该调用CFrameWnd::OnInitMenuPopup,它将基于ON_UPDATE_COMMAND_UI命令更新菜单项。

请确保将ON_COMMANDON_UPDATE_COMMAND_UI放在您的CDockablePaneCMDIFrameWndEx类中。示例:

BEGIN_MESSAGE_MAP(CMyDockablePane, CDockablePane)
    ON_COMMAND(ID_X, OnFoo)
    ON_UPDATE_COMMAND_UI(ID_X, OnFooUpdate)
    ...
END_MESSAGE_MAP()

...
//use AfxGetMainWnd() instead of this handle
popup->TrackPopupMenu(TPM_LEFTALIGN, pt.x, pt.y, AfxGetMainWnd());


另外,您可以从CTreeCtrl类中调用它,但是您必须覆盖OnInitMenuPopup
BEGIN_MESSAGE_MAP(CMyTreeCtrl, CTreeCtrl)
    ON_WM_INITMENUPOPUP()
    ON_COMMAND(ID_X, OnFoo)
    ON_UPDATE_COMMAND_UI(ID_X, OnFooUpdate)
    ...
END_MESSAGE_MAP()

void CMyTreeCtrl::OnInitMenuPopup(CMenu* popup, UINT nIndex, BOOL bSysMenu)
{
    if(popup && !bSysMenu)
    {
        CCmdUI state;
        state.m_pMenu = popup;
        state.m_nIndexMax = popup->GetMenuItemCount();
        for(UINT i = 0; i < state.m_nIndexMax; i++)
        {
            state.m_nIndex = i;
            state.m_nID = popup->GetMenuItemID(i);
            state.DoUpdate(this, FALSE);
        }
    }
}
...
//call from CMyTreeCtrl with this handle
popup->TrackPopupMenu(TPM_LEFTALIGN, pt.x, pt.y, this);

另请参见When Update Handlers Are Called

You cannot change the state of a menu item...

© www.soinside.com 2019 - 2024. All rights reserved.