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

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

Versión del producto original:   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 .NET Framework como el modelo de código nativo de Windows no administrado. La información de este artículo solo se aplica a código no administrado de Visual C++. Visual C++ 2005 admite tanto el modelo de código administrado proporcionado por .NET Framework como el modelo de código nativo de Windows no administrado.

El cambio del estado (habilitación o deshabilitación, activación/desactivación, cambio de texto) de un elemento de menú desde el controlador de interfaz de usuario (IU) de comando 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 CFrameWnd::OnInitMenuPopup función MFC recorre en iteración los elementos del menú y llama al controlador de la interfaz de usuario del comando de actualización para el elemento, si hay alguno. La apariencia de cada elemento de menú se actualiza para reflejar su estado (habilitado o deshabilitado, activado/desactivado).

El mecanismo de actualización de la interfaz de usuario no funciona para una aplicación basada en cuadros de diálogo porque no CDialog tiene ningún OnInitMenuPopup controlador y usa el controlador predeterminado de CWnd, que no llama a los controladores de interfaz de usuario de actualización de comandos 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 del cuadro de diálogo y copie el código siguiente:

    Nota

    Este código se ha tomado en gran parte 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

También se llama al controlador de interfaz de usuario de comandos de actualización desde para asegurarse de CWnd::OnCommand que no se haya deshabilitado el comando antes del enrutamiento. Por este motivo, 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 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. Cree una aplicación basada en cuadros de diálogo MFC mediante AppWizard.

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

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

  4. Agregue un UPDATE_COMMAND_UI controlador para el elemento de menú File/Exit . Para ello, haga clic con el botón secundario 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 CDialog clase derivada de Project. 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.