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

Este artículo le ayuda a resolver el problema que se produce cuando el menú está asociado a un cuadro de diálogo en Visual C++.

Versión original del producto:   Visual C++, .NET 2002
Número de KB original:   242577

Síntomas

Nota

Microsoft Visual C++ .NET 2002 y Visual C++ .NET 2003 admiten tanto el modelo de código administrado proporcionado por el .NET Framework como el modelo de código nativo Windows no administrado. La información de este artículo solo se aplica al código de Visual C++ no administrado. Visual C++ 2005 admite el modelo de código administrado proporcionado por el .NET Framework y el modelo de código Windows nativo no administrado.

Cambiar el estado (habilitar/deshabilitar, activar o desactivar, cambiar texto) de un elemento de menú desde su controlador de interfaz de usuario (UI) de comandos no funciona correctamente si el menú está asociado 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, el WM_INITMENUPOPUP mensaje se envía antes de mostrar los elementos de menú. La función MFC CFrameWnd::OnInitMenuPopup recorre en iteración los elementos de menú y llama al controlador de interfaz de usuario del comando update para el elemento, si hay uno. La apariencia de cada elemento de menú se actualiza para reflejar su estado (habilitado/deshabilitado, activado/desactivado).

El mecanismo de la interfaz de usuario de actualización no funciona para una aplicación basada en cuadros de diálogo porque CDialog no tiene ningún OnInitMenuPopup controlador y usa el controlador predeterminado de CWnd, que no llama a los controladores de interfaz de usuario del comando update para los elementos de menú.

Solución

Siga estos pasos para resolver este problema:

  1. Agregue una ON_WM_INITMENUPOPUP entrada al mapa de mensajes:

    BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
    // }} AFX_MSG_MAP
    
    ON_WM_INITMENUPOPUP()
    END_MESSAGE_MAP()
    
  2. Agregue una OnInitMenuPopup función miembro a la clase de cuadro de diálogo y copie el código siguiente:

    Nota

    Este código se toma en gran medida de CFrameWnd::OnInitMenuPopup en WinFrm.cpp)

void CTestDlg::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex,BOOL bSysMenu)
{
    // Make sure this is actually a menu. When clicking the program icon
    // in the window title bar this function will trigger and pPopupMenu 
    // will NOT be a menu.
    if (!IsMenu(pPopupMenu->m_hMenu))
        return;
        
    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

También se llama al controlador de interfaz de usuario del comando update desde CWnd::OnCommand para asegurarse de que el comando no se ha deshabilitado antes del enrutamiento. Esta es la razón por la que no se llama al controlador de comandos para un elemento de menú deshabilitado aunque no esté atenuado (no disponible). Los elementos de menú no se dibujan para reflejar su estado en este caso. Este es el código relacionado del 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. Cree una aplicación basada en diálogos MFC mediante AppWizard.

  2. Cree un nuevo recurso de menú y agréguele los elementos de menú Archivo y Archivo/Salir .

  3. Establezca este menú como el menú del cuadro de diálogo del cuadro de diálogo ventana Propiedades. Para ello, abra el recurso de cuadro de diálogo en el editor de diálogos. En la ventana Propiedades , haga clic en Menú. El identificador del nuevo recurso de menú se muestra en la lista desplegable Editor de propiedades de menú.

  4. Agregue un UPDATE_COMMAND_UI controlador para el elemento de menú Archivo/Salida . Para ello, haga clic con el botón derecho en Archivo/Salir en el editor de menús y, a continuación, haga clic en Agregar controlador de eventos. En el Asistente para controladores de eventos, agregue el UPDATE_COMMAND_UI controlador a la clase derivada del proyecto CDialog . Haga clic en Agregar y editar para crear el controlador y, a continuación, agregue una de estas instrucciones al método de controlador generado:

    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.
    
  5. Compile y ejecute la aplicación.