Compartir a través de


TN021: Enrutamiento de comandos y mensajes

Nota:

La nota técnica siguiente no se ha actualizado desde que se incluyó por primera vez en la documentación en línea. Como resultado, algunos procedimientos y temas podrían estar obsoletos o incorrectos. Para obtener la información más reciente, se recomienda buscar el tema de interés en el índice de documentación en línea.

En esta nota se describe la arquitectura de distribución y enrutamiento de comandos, así como temas avanzados en el enrutamiento de mensajes de ventana general.

Consulte Visual C++ para obtener información general sobre las arquitecturas descritas aquí, especialmente la distinción entre los mensajes de Windows, las notificaciones de control y los comandos. En esta nota se da por supuesto que está muy familiarizado con los problemas descritos en la documentación impresa y solo aborda temas muy avanzados.

La funcionalidad de enrutamiento de comandos y distribución de MFC 1.0 evoluciona a la arquitectura de MFC 2.0

Windows tiene el mensaje WM_COMMAND sobrecargado para proporcionar notificaciones de comandos de menú, teclas de aceleración y notificaciones de control de cuadro de diálogo.

MFC 1.0 se basa en eso un poco al permitir que un controlador de comandos (por ejemplo, "OnFileNew") en una CWnd clase derivada se llame en respuesta a un WM_COMMAND específico. Esto se integra mediante una estructura de datos denominada mapa de mensajes, lo que resulta en un mecanismo de comandos muy eficiente en términos de espacio.

MFC 1.0 también proporcionó funcionalidad adicional para separar las notificaciones de control de los mensajes de comando. Los comandos se representan mediante un identificador de 16 bits, a veces conocido como identificador de comando. Los comandos suelen partir de un elemento CFrameWnd (es decir, una selección de menú o un acelerador traducido), y se enrutan a una variedad de ventanas distintas.

MFC 1.0 usó el enrutamiento de comandos en un sentido limitado para la implementación de la interfaz de varios documentos (MDI). (Un delegado de ventana de marco MDI se rige por su ventana secundaria MDI activa correspondiente).

Esta funcionalidad se ha generalizado y ampliado en MFC 2.0 para permitir que los comandos se controlan mediante una gama más amplia de objetos (no solo objetos de ventana). Proporciona una arquitectura más formal y extensible para enrutar mensajes y reutiliza el enrutamiento de destino del comando para no solo el control de comandos, sino también para actualizar objetos de interfaz de usuario (como elementos de menú y botones de barra de herramientas) para reflejar la disponibilidad actual de un comando.

Identificadores de comandos

Vea Visual C++ para obtener una explicación del proceso de enrutamiento y enlace de comandos. La nota técnica 20 contiene información sobre la nomenclatura de identificadores.

Usamos el prefijo genérico "ID_" para los identificadores de comando. Los identificadores de comando son >= 0x8000. La línea de mensaje o la barra de estado mostrará la cadena de descripción del comando si hay un recurso STRINGTABLE con los mismos identificadores que el identificador de comando.

En los recursos de la aplicación, un identificador de comando puede aparecer en varios lugares:

  • En un recurso STRINGTABLE que tenga el mismo identificador que el mensaje de la línea de mensaje

  • Posiblemente, en muchos recursos de MENÚ asociados a elementos de menú que invocan el mismo comando

  • (AVANZADO) En un botón de cuadro de diálogo de un comando GOSUB

En el código fuente de la aplicación, un identificador de comando puede aparecer en varios lugares:

  • En el archivo RESOURCE.H (u otro archivo de encabezado de símbolo principal), para definir identificadores de comando específicos de la aplicación

  • QUIZÁS En una matriz de identificadores usada para crear una barra de herramientas.

  • En una macro ON_COMMAND

  • TALVEZ en una macro de ON_UPDATE_COMMAND_UI.

Actualmente, la única implementación en MFC que requiere que los identificadores de comando sean >= 0x8000 es la implementación de diálogos o comandos GOSUB.

Comandos GOSUB, uso de la arquitectura de comandos en cuadros de diálogo

La arquitectura de comandos de enrutamiento y habilitación de comandos funciona bien con ventanas de marco, elementos de menú, botones de barra de herramientas, botones de barra de diálogo, otras barras de control y otros elementos de interfaz de usuario diseñados para actualizar comandos a petición y enrutar comandos o identificadores de control a un destino de comando principal (normalmente la ventana de marco principal). Ese destino de comando principal puede enrutar el comando o las notificaciones de control a otros objetos de destino de comando según corresponda.

Un cuadro de diálogo (modal o modeless) puede beneficiarse de algunas de las características de la arquitectura de comandos si asigna el identificador de control del control de diálogo al identificador de comando adecuado. La compatibilidad con diálogos no es automática, por lo que es posible que tenga que escribir código adicional.

Tenga en cuenta que, para que todas estas características funcionen correctamente, los identificadores de comando deben ser >= 0x8000. Dado que muchos diálogos se pueden enrutar al mismo marco, los comandos compartidos deben ser >= 0x8000, mientras que los IDC no compartidos de un cuadro de diálogo específico deben ser <= 0x7FFF.

Puede colocar un botón estándar en un cuadro de diálogo modal estándar, con el IDC del botón configurado en el ID de comando apropiado. Cuando el usuario selecciona el botón, el propietario del cuadro de diálogo (normalmente la ventana de marco principal) obtiene el comando igual que cualquier otro comando. Esto se denomina comando GOSUB, ya que normalmente se usa para abrir otro cuadro de diálogo (un GOSUB del primer cuadro de diálogo).

También puede llamar a la función CWnd::UpdateDialogControls en su cuadro de diálogo y pasarle la dirección de la ventana principal del marco. Esta función habilitará o deshabilitará los controles de diálogo en función de si tienen controladores de comandos en el marco. La llamada a esta función se realiza automáticamente en el caso de las barras de control del bucle inactivo de la aplicación, pero hay que llamarla de manera directa en el caso de los cuadros de diálogo normales que queremos que tengan esta característica.

Cuando se llama a ON_UPDATE_COMMAND_UI

Mantener el estado habilitado o comprobado de todos los elementos de menú de un programa todo el tiempo puede ser un problema computacionalmente costoso. Una técnica común es habilitar o comprobar elementos de menú solo cuando el usuario selecciona el POPUP. La implementación de MFC 2.0 de CFrameWnd maneja el mensaje WM_INITMENUPOPUP y utiliza la arquitectura de enrutamiento de comandos para determinar los estados de los menús a través de los manejadores ON_UPDATE_COMMAND_UI.

CFrameWnd también controla el mensaje de WM_ENTERIDLE para describir el elemento de menú actual seleccionado en la barra de estado (también conocido como línea de mensaje).

La estructura de menús de una aplicación, editada por Visual C++, se utiliza para representar los posibles comandos disponibles en el momento de WM_INITMENUPOPUP. Los identificadores ON_UPDATE_COMMAND_UI pueden modificar el estado o el texto de un menú o, en un uso más avanzado (como la lista de archivos usados recientemente o el menú emergente de verbos OLE), permiten modificar de facto la estructura de menús antes de dibujar el menú.

El mismo tipo de procesamiento ON_UPDATE_COMMAND_UI se realiza para las barras de herramientas (y otras barras de control) cuando la aplicación entra en su bucle inactivo. Consulte la Referencia de la biblioteca de clases y la nota técnica 31 para obtener más información sobre las barras de control.

Menús emergentes anidados

Si estás utilizando una estructura de menú anidada, notarás que el manejador de ON_UPDATE_COMMAND_UI para el primer elemento en el menú emergente se activa en dos situaciones diferentes.

En primer lugar, se llama en relación con el propio menú emergente en sí. Esto es necesario porque los menús emergentes no tienen identificadores y usamos el identificador del primer elemento de menú del menú emergente para hacer referencia a todo el menú emergente. En este caso, la variable miembro m_pSubMenu del CCmdUI objeto no será NULL y apuntará al menú emergente.

En segundo lugar, se llama justo antes de que se dibujen los elementos de menú del menú emergente. En este caso, el identificador hace referencia solo al primer elemento de menú y la variable miembro m_pSubMenu del CCmdUI objeto será NULL.

Esto le permite habilitar el menú emergente distinto de sus elementos de menú, pero requiere que escriba código compatible con el menú. Por ejemplo, en un menú anidado con la siguiente estructura:

File>
    New>
    Sheet (ID_NEW_SHEET)
    Chart (ID_NEW_CHART)

Los comandos ID_NEW_SHEET y ID_NEW_CHART se pueden habilitar o deshabilitar de forma independiente. El menú emergente Nuevo debe estar habilitado si alguno de los dos está habilitado.

El controlador de comandos para ID_NEW_SHEET (el primer comando del elemento emergente) tendría un aspecto similar al siguiente:

void CMyApp::OnUpdateNewSheet(CCmdUI* pCmdUI)
{
    if (pCmdUI->m_pSubMenu != NULL)
    {
        // enable entire pop-up for "New" sheet and chart
        BOOL bEnable = m_bCanCreateSheet || m_bCanCreateChart;
        // CCmdUI::Enable is a no-op for this case, so we
        // must do what it would have done.
        pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
            MF_BYPOSITION |
            (bEnable  MF_ENABLED : (MF_DISABLED | MF_GRAYED)));

        return;
    }
    // otherwise just the New Sheet command
    pCmdUI->Enable(m_bCanCreateSheet);
}

El controlador de comandos para ID_NEW_CHART sería un controlador de comandos de actualización normal y tendría un aspecto similar al siguiente:

void CMyApp::OnUpdateNewChart(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bCanCreateChart);
}

ON_COMMAND y ON_BN_CLICKED

Las macros de mapa de mensajes para ON_COMMAND y ON_BN_CLICKED son las mismas. El mecanismo de enrutamiento de notificaciones de control y comando MFC solo usa el identificador de comando para decidir a dónde enrutar. Las notificaciones de control con código de notificación de control de cero (BN_CLICKED) se interpretan como comandos.

Nota:

De hecho, todos los mensajes de notificación de control pasan por la cadena del controlador de comandos. Por ejemplo, técnicamente es posible escribir un controlador de notificaciones de control para EN_CHANGE en la clase de documento. Esto no suele ser aconsejable porque las aplicaciones prácticas de esta característica son pocas, la característica no es compatible con ClassWizard y el uso de la característica puede dar lugar a código frágil.

Deshabilitar la deshabilitación automática de controles de botón

Si coloca un control de botón en una barra de diálogo o en un cuadro de diálogo mediante el que llama a CWnd::UpdateDialogControls por su cuenta, observará que los botones que no tienen ON_COMMAND o ON_UPDATE_COMMAND_UI controladores se deshabilitarán automáticamente por el marco. En algunos casos, no necesitará un gestor de eventos, pero querrá que el botón permanezca activo. La manera más fácil de lograr esto es agregar un controlador de comandos ficticio (fácil de hacer con ClassWizard) y no hacer nada en él.

Enrutamiento de mensajes de ventana

A continuación se describen algunos temas más avanzados sobre las clases MFC y cómo el enrutamiento de mensajes de Windows y otros temas los afectan. La información aquí solo se describe brevemente. Consulte la Referencia de la Biblioteca de Clases para obtener más información sobre las API públicas. Consulte el código fuente de la biblioteca MFC para obtener más información sobre los detalles de implementación.

Consulte la Nota técnica 17 para obtener más información sobre la limpieza de ventanas, un tema muy importante para todas las clases derivadas de CWnd.

Problemas de CWnd

La función miembro de implementación CWnd::OnChildNotify proporciona una arquitectura eficaz y ampliable de ventanas secundarias (también conocidas como controles) para estar al tanto de los mensajes, comandos y notificaciones de control que van a su ventana primaria (o "propietaria"). Si la ventana secundaria (/control) es un objeto de C++ CWnd, primero se llama a la función virtual OnChildNotify con los parámetros del mensaje original (es decir, una estructura MSG). La ventana secundaria puede dejar el mensaje, omitirlo o modificarlo para el elemento primario (poco frecuente).

La implementación predeterminada de CWnd controla los siguientes mensajes y usa el enlace OnChildNotify para permitir que las ventanas secundarias (controles) accedan primero en el mensaje:

  • WM_MEASUREITEM y WM_DRAWITEM (para auto-dibujo)

  • WM_COMPAREITEM y WM_DELETEITEM (para dibujo automático)

  • WM_HSCROLL y WM_VSCROLL

  • WM_CTLCOLOR

  • WM_PARENTNOTIFY

Verá que OnChildNotify se usa para cambiar los mensajes de dibujo de propietario por mensajes de dibujo automático.

Además de OnChildNotify, los mensajes de desplazamiento tienen un comportamiento de enrutamiento adicional. Por favor, consulte más abajo para obtener más información sobre las barras de desplazamiento y los orígenes de WM_HSCROLL y WM_VSCROLL mensajes.

Problemas de CFrameWnd

La clase CFrameWnd proporciona la mayor parte del enrutamiento de comandos y la implementación de actualización de la interfaz de usuario. Esto se usa principalmente para la ventana de marco principal de la aplicación (CWinApp::m_pMainWnd), pero se aplica a todas las ventanas de marco.

La ventana del marco principal es la ventana con la barra de menús y es el elemento primario de la barra de estado o la línea de mensaje. Consulte la explicación anterior sobre el enrutamiento de comandos y WM_INITMENUPOPUP.

La clase CFrameWnd proporciona administración de la vista activa. Los siguientes mensajes son dirigidos a través de la vista activa.

  • Todos los mensajes de comando (la vista activa obtiene primero acceso a ellos).

  • Los mensajes WM_HSCROLL y WM_VSCROLL de las barras de desplazamiento adyacentes (ver más abajo).

  • WM_ACTIVATE (y WM_MDIACTIVATE para MDI) se convierten en llamadas a la función virtual CView::OnActivateView.

Problemas de CMDIFrameWnd/CMDIChildWnd

Ambas clases de ventana de marco MDI derivan de CFrameWnd y, por tanto, están habilitadas para el mismo tipo de enrutamiento de comandos y actualización de la interfaz de usuario proporcionada en CFrameWnd. En una aplicación MDI típica, solo la ventana de marco principal (es decir, el objeto CMDIFrameWnd ) contiene la barra de menús y la barra de estado y, por tanto, es el origen principal de la implementación de enrutamiento de comandos.

El esquema de enrutamiento general es que la ventana secundaria MDI activa obtiene el primer acceso a los comandos. Las funciones PreTranslateMessage predeterminadas controlan las tablas de aceleración para las ventanas secundarias de MDI (primero) y el marco MDI (segundo), así como los aceleradores estándar de comandos del sistema MDI normalmente administrados por TranslateMDISysAccel (último).

Problemas de la barra de desplazamiento

Al controlar el mensaje de desplazamiento (WM_HSCROLL/OnHScroll o WM_VSCROLL/OnVScroll), debe intentar escribir el código del controlador para que no dependa de dónde procede el mensaje de la barra de desplazamiento. Esto no es solo un problema general de Windows, ya que los mensajes de desplazamiento pueden provenir de controles de barra de desplazamiento verdaderos o de WS_HSCROLL/WS_VSCROLL barras de desplazamiento que no son controles de barra de desplazamiento.

MFC amplía esto para permitir que los controles de barra de desplazamiento sean secundarios o del mismo nivel de la ventana de desplazamiento (de hecho, la relación primario/secundario entre la barra de desplazamiento y la ventana de desplazamiento puede ser cualquier cosa). Esto es especialmente importante para las barras de desplazamiento compartidas con ventanas divisoras. Consulte la Nota técnica 29 para obtener más información sobre la implementación de CSplitterWnd , incluida más información sobre los problemas de la barra de desplazamiento compartido.

Aparte, hay dos clases derivadas de CWnd en las que los estilos de barra de desplazamiento especificados en tiempo de creación quedan atrapados y no se pasan a Windows. Cuando se pasa a una rutina de creación, WS_HSCROLL y WS_VSCROLL se pueden establecer de forma independiente, pero después de la creación no se puede cambiar. Sobra decir que no debe comprobar ni establecer directamente los bits de estilo de WS_SCROLL de la ventana creada.

En CMDIFrameWnd, los estilos de barra de desplazamiento que se pasan a Create o a LoadFrame se usan para crear MDICLIENT. Si quieres tener un área MDICLIENT desplazable (como el Administrador de programas de Windows) asegúrate de establecer ambos estilos de barra de desplazamiento (WS_HSCROLL | WS_VSCROLL) para el estilo usado para crear cmDIFrameWnd.

Para CSplitterWnd , los estilos de barra de desplazamiento se aplican a las barras de desplazamiento compartidas especiales para las regiones divisoras. En el caso de las ventanas de división estáticas, normalmente no se establecerá ningún estilo para las barras de desplazamiento. En el caso de las ventanas divisoras dinámicas, normalmente tendrá el estilo de barra de desplazamiento establecido para la dirección que dividirá, es decir, WS_HSCROLL si puede dividir filas, WS_VSCROLL si puede dividir columnas.

Consulte también

Notas técnicas por número
Notas Técnicas por Categoría