如果菜单附加到 Visual C++ 中的对话框,则无法从其命令用户界面处理程序更改菜单项的状态


注意Microsoft Visual C++.NET 2002年和 Microsoft Visual C++.NET 2003年支持托管的代码模型,它是由 Microsoft.NET Framework 以及非托管本机 Microsoft Windows 代码模型。这篇文章中的信息仅适用于非托管 Visual C++ 代码。Microsoft Visual C++ 2005年支持通过 Microsoft.NET Framework 提供的托管的代码模型和非托管的本机 Microsoft Windows 代码模型。

症状


从其命令的用户界面 (UI) 处理程序更改的菜单项状态 (启用/禁用、 选中/取消选中,更改文本) 将无法正常运行如果菜单被连接到一个对话框:
void CTestDlg::OnUpdateFileExit(CCmdUI* pCmdUI) {    pCmdUI->Enable(FALSE); //Not calling the command handler, but does not show as disabled.    pCmdUI->SetCheck(TRUE); // Does not show check mark before the text.    pCmdUI->SetRadio(TRUE); // Does not show dot before the text.    pCmdUI->SetText("Close"); //Does not change the text.}

原因


下拉菜单中显示时,在显示的菜单项之前发送 WM_INITMENUPOPUP 消息。MFC CFrameWnd::OnInitMenuPopup函数循环访问菜单项和项,调用更新命令 UI 处理程序,如果有的话。每个菜单项的外观将更新,以反映它的状态 (启用/禁用、 选中/未选中)。更新用户界面机制不适合对话框基于应用程序由于CDialog没有OnInitMenuPopup处理程序,它使用CWnd默认处理程序,它不会为菜单项调用更新命令 UI 处理程序。

解决方案


使用以下步骤解决此问题:
  1. ON_WM_INITMENUPOPUP条目添加到消息映射:
    BEGIN_MESSAGE_MAP(CTestDlg, CDialog)//}}AFX_MSG_MAPON_WM_INITMENUPOPUP()END_MESSAGE_MAP()
  2. OnInitMenuPopup成员函数添加到对话框类和复制下面的代码 (请注意,此代码将很大程度上由在 WinFrm.cpp CFrameWnd::OnInitMenuPopup):
    void CTestDlg::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex,BOOL bSysMenu){    ASSERT(pPopupMenu != NULL);    // Check the enabled state of various menu items.    CCmdUI state;    state.m_pMenu = pPopupMenu;    ASSERT(state.m_pOther == NULL);    ASSERT(state.m_pParentMenu == NULL);    // Determine if menu is popup in top-level menu and set m_pOther to    // it if so (m_pParentMenu == NULL indicates that it is secondary popup).    HMENU hParentMenu;    if (AfxGetThreadState()->m_hTrackingMenu == pPopupMenu->m_hMenu)        state.m_pParentMenu = pPopupMenu;    // Parent == child for tracking popup.    else if ((hParentMenu = ::GetMenu(m_hWnd)) != NULL)    {        CWnd* pParent = this;           // Child windows don't have menus--need to go to the top!        if (pParent != NULL &&           (hParentMenu = ::GetMenu(pParent->m_hWnd)) != NULL)        {           int nIndexMax = ::GetMenuItemCount(hParentMenu);           for (int nIndex = 0; nIndex < nIndexMax; nIndex++)           {            if (::GetSubMenu(hParentMenu, nIndex) == pPopupMenu->m_hMenu)            {                // When popup is found, m_pParentMenu is containing menu.                state.m_pParentMenu = CMenu::FromHandle(hParentMenu);                break;            }           }        }    }    state.m_nIndexMax = pPopupMenu->GetMenuItemCount();    for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;      state.m_nIndex++)    {        state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);        if (state.m_nID == 0)           continue; // Menu separator or invalid cmd - ignore it.        ASSERT(state.m_pOther == NULL);        ASSERT(state.m_pMenu != NULL);        if (state.m_nID == (UINT)-1)        {           // Possibly a popup menu, route to first item of that popup.           state.m_pSubMenu = pPopupMenu->GetSubMenu(state.m_nIndex);           if (state.m_pSubMenu == NULL ||            (state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||            state.m_nID == (UINT)-1)           {            continue;       // First item of popup can't be routed to.           }           state.DoUpdate(this, TRUE);   // Popups are never auto disabled.        }        else        {           // Normal menu item.           // Auto enable/disable if frame window has m_bAutoMenuEnable           // set and command is _not_ a system command.           state.m_pSubMenu = NULL;           state.DoUpdate(this, FALSE);        }        // Adjust for menu deletions and additions.        UINT nCount = pPopupMenu->GetMenuItemCount();        if (nCount < state.m_nIndexMax)        {           state.m_nIndex -= (state.m_nIndexMax - nCount);           while (state.m_nIndex < nCount &&            pPopupMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)           {            state.m_nIndex++;           }        }        state.m_nIndexMax = nCount;    }}

状态


此行为是设计使然。

更多信息


更新命令 UI 处理程序也被称为从CWnd::OnCommand ,以确保命令在传送之前未被禁用。这就是原因命令处理程序不调用为禁用的菜单项即使不会变灰 (不可用)。不绘制菜单项在这种情况下反映其状态。这是 Wincore.cpp 文件中的相关的代码:
   // Make sure command has not become disabled before routing.   CTestCmdUI state;   state.m_nID = nID;   OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL);   if (!state.m_bEnabled)   {      TRACE1("Warning: not executing disabled command %d\n", nID);      return TRUE;   }

再现现象的步骤

请按照下列步骤来重现这种现象在 Visual C++.NET:
  1. 通过使用应用程序向导创建的 MFC 基于对话框的应用程序。
  2. 创建新的菜单资源,并向其中添加的文件文件/退出菜单项。
  3. 设置对话框中的属性窗口中对话框中的菜单该菜单。若要执行此操作,请在对话框编辑器中打开对话框资源。在属性窗口中,单击选择菜单。在菜单属性编辑器下拉列表中显示的新菜单资源的 ID。
  4. 添加文件/退出菜单项 UPDATE_COMMAND_UI 处理程序。若要执行此操作,请在菜单编辑器中右击文件/退出,然后单击添加事件处理程序。在事件处理程序向导向项目CDialog派生的类中添加 UPDATE_COMMAND_UI 处理程序。单击添加并编辑创建处理程序,并将这些语句中的一个添加到生成的处理程序方法:
    pCmdUI->Enable(FALSE); //Not calling the handler, but does not show as disabledpCmdUI->SetCheck(TRUE); // Does not show check mark before the text.pCmdUI->SetRadio(TRUE); // Does not show dot before the text.pCmdUI->SetText("Close"); //Does not change the text.
  5. 生成并运行该应用程序。

参考


有关更多信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
141751添加到 MFC 中的对话框的控制条