Как получить ON_UPDATE_COMMAND_UI в CTreeCtrl / CWnd? - PullRequest
3 голосов
/ 08 октября 2019

Я работаю над приложением MFC MDI. Я хотел бы выборочно включать или отключать некоторые команды контекстного меню, вызываемые правой кнопкой мыши, но команды меню остаются активными после того, как я реализовал функциональность, используя ON_UPDATE_COMMAND_UI для отключения определенных команд.

Похоже, что сообщение ON_UPDATE_COMMAND_UI не запускается илиобрабатываются в соответствующее время.

Цель состоит в том, чтобы реализовать контекстное меню CTreeCtrl, которое зависит от выбранного элемента дерева .

Моя структура приложения

Основной CWinAppEx запускает CMDIFrameWndEx. Объект CMDIFrameWndEx содержит обычные дочерние кадры MDI, но он также содержит CDockablePane, который сам содержит CTreeCtrl, предназначенный для аналогичного использования в виде дерева проекта или решения в Visual Studio.

enter image description here

В моем классе реализованы обработчики сообщений ON_UPDATE_COMMAND_UI, унаследованные от CTreeCtrl. Эти обработчики вызываются, когда я нажимаю на сами пункты меню , но это слишком поздно;они должны были быть вызваны до открытия меню.

Я подозреваю, что это связано с маршрутизацией команд (или сообщений);Поиск в Google указывает, что некоторые элементы управления или классы окон не получают сообщений 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 );
}

Новый код

С тех пор я добавил переопределения для OnCmdMsg в мой класс, унаследованный от CMDIFrameWndEx (CMainFrame), и в мой класс, унаследованный от CDockablePane (LCPSelector). Намерение состоит в том, чтобы передавать командные сообщения до объекта 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);
}

1 Ответ

2 голосов
/ 08 октября 2019

Когда меню открыто, генерируется сообщение 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_COMMAND и ON_UPDATE_COMMAND_UI в вашем классе CDockablePane или CMDIFrameWndEx. Пример:

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

См. Также
При вызове обработчиков обновлений

Нельзя изменить состояние пункта меню ...

...