No puede cambiar el estado de un elemento de menú desde su controlador de interfaz de usuario de comandos si el menú está vinculado a un cuadro de diálogo en Visual C++


Nota Microsoft Visual C++ .NET 2002 y Visual C++ .NET 2003 proporciona compatibilidad con el modelo tanto en el código administrado el Microsoft.NET Framework y el modelo de código no administrado nativo de Microsoft Windows. La información de este artículo sólo se aplica a código no administrado de Visual C++. Microsoft Visual C++ 2005 admite el modelo de código administrado que es proporcionado por el Microsoft.NET Framework y el modelo de código nativo Microsoft Windows no administrado.

Síntomas


Cambiar el estado (habilitar/deshabilitar, marque o desmarque, cambiar texto) de un elemento de menú desde su controlador de interfaz de usuario (IU) de comandos no funciona correctamente si el menú está vinculado a un cuadro de diálogo:
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.}

Causa


Cuando se muestra un menú desplegable, se envía el mensaje WM_INITMENUPOPUP antes de presentar los elementos de menú. La función MFC CFrameWnd::OnInitMenuPopup recorre en iteración los elementos de menú y llama el controlador de interfaz de usuario de comandos de actualización para el artículo, si hay alguno. El aspecto de cada elemento de menú se actualiza para reflejar su estado (habilitado o deshabilitado, activado, desactivado). El mecanismo de interfaz de usuario de actualización no funciona para una aplicación basada en el cuadro de diálogo porque CDialog no tiene ningún controlador de OnInitMenuPopup y se utiliza el controlador predeterminado de CWnd, que no se llame a controladores de interfaz de usuario de comandos de actualización para elementos de menú.

Resolución


Para resolver este problema, siga estos pasos:
  1. Agregue una entrada ON_WM_INITMENUPOPUP al mapa de mensajes:
    BEGIN_MESSAGE_MAP(CTestDlg, CDialog)//}}AFX_MSG_MAPON_WM_INITMENUPOPUP()END_MESSAGE_MAP()
  2. Agregar una función miembro de OnInitMenuPopup a la clase del cuadro de diálogo y copie el código siguiente (Observe que este código se toma en gran medida de CFrameWnd::OnInitMenuPopup en 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;    }}

Estado


Este comportamiento es una característica del diseño de la aplicación.

Más información


El controlador de interfaz de usuario de comandos de actualización es el acrónimo de CWnd:: OnCommand para asegurarse de que no se desactiva comando antes de la distribución. Por esta razón el controlador de comandos no se llama para un elemento de menú deshabilitado aunque no aparece atenuada (no disponible). Elementos de menú no se dibujan para reflejar su estado en este caso. Este es el código relacionado desde el archivo 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;   }

Pasos para reproducir el comportamiento

Siga estos pasos para reproducir este comportamiento en Visual C++. NET:
  1. Crear una aplicación basada en cuadros de diálogo MFC mediante el Asistente para aplicaciones.
  2. Crear un nuevo recurso de menú y agregar los elementos de menú archivo y Archivo/salir a él.
  3. Establecer este menú como el menú del cuadro de diálogo en la ventana de propiedades del cuadro de diálogo. Para ello, abra el recurso de cuadro de diálogo en el editor. En la ventana Propiedades, haga clic en seleccionar menú. El identificador del nuevo recurso de menú se muestra en la lista de desplegable menú editor de propiedad.
  4. Agregue un controlador UPDATE_COMMAND_UI para el elemento de menú File/Exit . Para hacer esto, clic en Archivo/salir en el editor de menús y, a continuación, haga clic en Agregar controlador de eventos. En el Asistente para controlador de eventos, agregue el controlador UPDATE_COMMAND_UI al proyecto de clase derivada de CDialog . Haga clic en Agregar y editar para crear el controlador y, a continuación, agregue una de estas instrucciones al método controlador generado:
    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. Genere y ejecute la aplicación.

Referencias


Para obtener más información, haga clic en el número de artículo siguiente para verlo en Microsoft Knowledge Base:
141751 agregar barras de controles a cuadros de diálogo de MFC