Update Command UI Handlers Do Not Work for Menu Attached to a Dialog Box

PRB: Update Command UI Handlers Do Not Work for Menu Attached to a Dialog Box  
ID: Q242577  

--------------------------------------------------------------------------------
The information in this article applies to:

Microsoft Visual C++, 32-bit Editions, version 6.0, used with:
The Microsoft Foundation Classes (MFC)

--------------------------------------------------------------------------------

SYMPTOMS
Changing the state (enable/disable, check/uncheck, change text) of a menu item from its command user-interface (UI) handler does not work correctly if the menu is attached to a dialog box:  

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.
}  

CAUSE
When a drop-down menu is displayed, the WM_INITMENUPOPUP message is sent prior to displaying the menu items. The MFC CFrameWnd::OnInitMenuPopup function iterates through the menu items and calls the update command UI handler for the item, if there is one. The appearance of each menu item is updated to reflect its state (enabled/disabled, checked/unchecked).

The update UI mechanism doesn't work for a dialog box-based application because CDialog has no OnInitMenuPopup handler and it uses CWnd's default handler, which does not call update command UI handlers for menu items.  

RESOLUTION
Use the following steps to resolve this problem:  

Add an ON_WM_INITMENUPOPUP entry to the message map:  

BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
//{{AFX_MSG_MAP(CTestDlg)
                  ........................
                  ........................
//}}AFX_MSG_MAP

ON_WM_INITMENUPOPUP()
END_MESSAGE_MAP()  

Add a OnInitMenuPopup member function to your dialog box class and copy the following code (note that this code is taken largely from CFrameWnd::OnInitMenuPopup in WinFrm.cpp):  

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

STATUS
This behavior is by design.  

MORE INFORMATION
The update command UI handler is also called from CWnd::OnCommand to make sure that command has not become disabled before routing. This is why the command handler is not called for a disabled menu item even though it is not grayed (unavailable). Menu items are not drawn to reflect their status in this case. This is the related code from the Wincore.cpp file:  

    // 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;
    }  
Steps to Reproduce Behavior
Create a dialog-based application by using AppWizard.

Create a new menu resource and add the File and File/Exit menu items to it.

Set this menu as the menu for the dialog box in properties.

Add an UPDATE_COMMAND_UI handler for File/Exit menu item by using ClassWizard, and then add either one of the statements to the handler.

pCmdUI->Enable(FALSE); //Not calling the 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.  

Build and run the application.

REFERENCES
For additional information, click the article number below to view the article in the Microsoft Knowledge Base:  

Q141751 SAMPLE: Adding Control Bars to Dialog Boxes in MFC  
&copy; Microsoft Corporation 9/29/1999, All Rights Reserved.
Contributions by Sreedhar Pelluru, Microsoft Corporation

Additional query words: WM_INITMENUPOPUP ON_UPDATE_COMMAND_UI  

Keywords : kbDlg kbMenu kbMFC KbUIDesign kbVC600 kbDSupport kbGrpMFCATL  
Version : winnt:6.0  
Platform : winnt  
Issue type : kbprb  
Technology : kbvc  

Last Reviewed: February 4, 2000
&copy; 2000 Microsoft Corporation. All rights reserved. Terms of Use.

--------------------------------------------------------------------------------
Send feedback to MSDN.Look here for MSDN Online resources.

症状
从命令用户界面处理函数(Command UI handler)改变菜单状态(启用/禁用,选择/取消选择,更改文字)在由对话框处理时没有正常工作.
void CTestDlg::OnUpdateFileExit(CCmdUI* pCmdUI   
{   
    pCmdUI->Enable(FALSE); //没有显示为禁用.   
    pCmdUI->SetCheck(TRUE); // 没有文字前显示选定标记.   
    pCmdUI->SetRadio(TRUE); // 没有在文字前显示点.   
    pCmdUI->SetText("Close"); //没有更改菜单文字.   
}  
void CTestDlg::OnUpdateFileExit(CCmdUI* pCmdUI
{
    pCmdUI->Enable(FALSE); //没有显示为禁用.
    pCmdUI->SetCheck(TRUE); // 没有文字前显示选定标记.
    pCmdUI->SetRadio(TRUE); // 没有在文字前显示点.
    pCmdUI->SetText("Close"); //没有更改菜单文字.
}
原因
在下拉菜单显示的时候, WM_INITMENUPOPUP消息被先发送以显示菜单项.MFC CFrameWnd::OnInitMenuPopup 函数遍历菜单项并为每个菜单项调用更新命令处理函数(如果有的话).菜单的外观被更新以反映它的状态(启用/禁用,选择/取消选择)更新用户界面机制在基于对话框的应用程序中不能工作,因为CDialog没有OnInitMenuPopup 处理函数,而使用CWnd's 默认处理函数,该函数没有为菜单项调用更新命令处理函数.
解决
适用下列步骤解决此问题在消息映射中添加ON_WM_INITMENUPOPUP 项:
BEGIN_MESSAGE_MAP(CTestDlg, CDialog)   
//{{AFX_MSG_MAP(CTestDlg)   
                        ........................   
                        ........................   
//}}AFX_MSG_MAP   
ON_WM_INITMENUPOPUP()   
END_MESSAGE_MAP()  
在你的对话框类中添加OnInitMenuPopup成员函数且复制下列代码到该函数(注意:代码基本上是从CFrameWnd::OnInitMenuPopup(在WinFrm.cpp中)复制过来的):
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;   
    }   
}  
状态
设计上行为如此.
更多信息
命令用户界面处理函数也被CWnd::OnCommand 调用以确认命令在传递之前没有被禁用.这就是禁用的菜单项的命令处理没有被调用的原因(虽然没有以灰色显示(不可用)).在这种情况下,菜单项没有被重画以反映菜单项的状态.这是Wincore.cpp 文件中的相关代码:
   //在传递命令之前,确定命令没有被禁用
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;   
}  
重现此行为的步骤
使用应用程序向导建立一个基于对话框的应用程序建立一个新的菜单资源,并且向其上添加文件和文件/退出菜单项.在对话框的属性中设置对话框的菜单为上述菜单.使用类向导为文件/退出菜单项添加一个UPDATE_COMMAND_UI处理并添加下列语句之一到处理函数.
pCmdUI->Enable(FALSE); //没有显示为禁用.   
pCmdUI->SetCheck(TRUE); // 没有文字前显示选定标记.   
pCmdUI->SetRadio(TRUE); // 没有在文字前显示点.   
pCmdUI->SetText("Close"); //没有更改菜单文字.  
编译运行此程序.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值