I'm working on an MFC MDI application. I would like to selectively enable or disable some right-click context menu commands, but the menu commands remain active after I implemented functionality using ON_UPDATE_COMMAND_UI to disable certain commands.
It appears that the ON_UPDATE_COMMAND_UI message is not being triggered or handled at the appropriate time.
The goal is to implement a CTreeCtrl context-menu which depends on the selected tree element.
My application structure
The main CWinAppEx launches a CMDIFrameWndEx. The CMDIFrameWndEx object contains normal MDI child-frames, but it also contains a CDockablePane which itself contains a CTreeCtrl, intended to be used similiarly to the Project or Solution tree-view in Visual Studio.
I've implemented ON_UPDATE_COMMAND_UI message-handlers in my class inheriting from CTreeCtrl. These handlers are being called when I click the menu-items themselves, but this is too late; they should have been called before the menu opens.
I suspect that this is related to command (or message) routing; Googling around indicates that some control or window-classes do not receive ON_COMMAND_UPDATE_UI messages because they're handled at the CFrameWnd level. However, the work-arounds or solutions presented in those discussions are not clearly articulated. I want to adhere to common MFC/MDI idioms, so I'm hoping for a somewhat-comprehensive, beginner-level explanation of this issue.
Are CDockablePane windows (or CTreeCtrl controls) not intended to interact with ON_COMMAND_UPDATE_UI? Why is this? Am I missing something else?
My instinct is that this should be possible without wiring up a bunch of events myself, because the MFC framework is supposed to deliver messages to any classes which may handle them. I am starting to think there is some subtlety to this behavior that I'm missing.
Code
From my CTreeCtrl-inheriting class (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 );
}
New code
I have since added overrides for OnCmdMsg to my CMDIFrameWndEx-inherited class (CMainFrame), and to my CDockablePane-inherited class (LCPSelector). The intention is to pass command-messages all the way down to the CTreeCtrl object in case they may be handled.
I added this code based on the discussion of command-routing here:
https://docs.microsoft.com/en-us/cpp/mfc/command-routing?view=vs-2019
Still getting the same results, though. Maybe this is the wrong direction, or maybe I'm missing something else too.
For reference: CMainFrame inherits from CMDIFrameWndEx, and LCPSelector inherits from 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);
}
pPopup->EnableMenuItem(menu_item_id, MF_ENABLED/ MF_GRAYED)
before callingpPopup->TrackPopupMenu
– Barmak ShemiraniON_UPDATE_COMMAND_UI
is called from an idle loop. – Mark Ransom