Compartir vía


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 ser incorrectos. Para obtener información más reciente, se recomienda buscar el tema de interés en el índice de la documentación en línea.

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

Consulte Visual C++ para obtener información general sobre las arquitecturas que se describen aquí, especialmente la distinción entre mensajes de Windows, notificaciones de control y comandos. En esta nota se supone que tiene un profundo conocimiento de los problemas descritos en la documentación impresa, ya que en ella solo se abordan temas muy avanzados.

La funcionalidad de distribución y enrutamiento de comandos de MFC 1.0 evoluciona a 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 basaba en cierto modo en esto, ya que permite que pueda llamarse a un controlador de comandos (por ejemplo, "OnFileNew") de una clase derivada CWnd en respuesta a un comando WM_COMMAND específico. Esto va parejo a una estructura de datos denominada asignación de mensajes, y da como resultado un mecanismo de comandos muy eficiente con el espacio.

MFC 1.0 también proporcionaba una funcionalidad adicional para separar las notificaciones de control de los mensajes de comandos. Los comandos se representan mediante un identificador de 16 bits, a veces denominado 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.

En MFC 1.0 se usaba el enrutamiento de comandos en un sentido limitado para la implementación de interfaces de múltiples 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 controlen mediante una gama más amplia de objetos (no solo objetos de ventana). Así, proporciona una arquitectura más formal y extensible para enrutar mensajes, y reutiliza el enrutamiento de destinos de comandos no solo para controlar los comandos, sino también para actualizar objetos de la interfaz de usuario (como elementos de menú o botones de barra de herramientas) de forma que se refleje la disponibilidad actual de un comando.

Identificadores de comando

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

En los identificadores de comando usamos el prefijo genérico "ID_". 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 existe 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 sitios:

  • 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 sitios:

  • 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

  • TAL VEZ en una matriz de identificadores usada para crear una barra de herramientas

  • En una macro ON_COMMAND

  • TAL VEZ en una macro 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 cuadros de diálogo/comandos de GOSUB.

Comandos GOSUB. Uso de la arquitectura de comandos en cuadros de diálogo

La arquitectura de comandos para enrutar y habilitar comandos funciona bien con ventanas de marco, con elementos de menú, con botones de barra de herramientas, con botones de barra de cuadro de diálogo y con 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 como corresponda.

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

Tenga en cuenta que, para que todas estas características funcionen correctamente, los identificadores de comando deben ser >= 0x8000. Dado que muchos cuadros de diálogo podrían enrutarse 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 normal en un cuadro de diálogo modal normal con el IDC del botón establecido en el identificador de comando adecuado. 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 (que es un elemento GOSUB del primer cuadro de diálogo).

También se puede llamar a la función CWnd::UpdateDialogControls en el cuadro de diálogo y pasarle la dirección de la ventana de marco principal. Esta función habilitará o deshabilitará los controles de cuadro de diálogo en función de si tienen identificadores de comando 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 de habilitado/comprobado de todos los elementos de menú de un programa todo el tiempo puede plantear un problema de consumo excesivo de recursos. Una técnica común es habilitar o comprobar elementos de menú solo cuando el usuario seleccione el elemento POPUP. La implementación de MFC 2.0 de CFrameWnd controla el mensaje WM_INITMENUPOPUP y usa la arquitectura de enrutamiento de comandos para determinar los estados de los menús a través de identificadores ON_UPDATE_COMMAND_UI.

CFrameWnd también controla el mensaje 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 usa para representar los posibles comandos disponibles cuando aparece el mensaje 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 con las barras de herramientas (y otras barras de control) cuando la aplicación entra en su bucle inactivo. Vea 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 usa una estructura de menús anidada, observará que el identificador ON_UPDATE_COMMAND_UI del primer elemento de menú del menú emergente se llama en dos casos 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 objeto CCmdUI 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 objeto CCmdUI será NULL.

Esto permite habilitar el menú emergente de forma distintiva con respecto a sus elementos de menú, pero requiere escribir código con el que identificar 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 e ID_NEW_CHART se pueden habilitar o deshabilitar de forma independiente. El menú emergente Nuevo debe estar habilitado si alguno de estos dos comandos se habilita.

El identificador de comando de 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 identificador de comando de ID_NEW_CHART sería un identificador de comando 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 asignación de mensaje de ON_COMMAND y ON_BN_CLICKED son las mismas. El mecanismo de enrutamiento de notificaciones de control y comando de MFC solo usa el identificador de comando para decidir hacia dónde enrutar. Las notificaciones de control con un código de notificación de control cero (BN_CLICKED) se interpretan como comandos.

Nota:

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

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

Si coloca un control de botón en una barra de cuadro de diálogo o en un cuadro de diálogo en el que está llamando a CWnd::UpdateDialogControls por su cuenta, verá que el marco deshabilitará automáticamente los botones que carezcan de los identificadores ON_COMMAND u ON_UPDATE_COMMAND_UI. En algunos casos, no será necesario tener un identificador, pero convendrá que el botón permanezca habilitado. La manera más fácil de lograrlo es agregar un identificador de comando ficticio (fácil de hacer con ClassWizard) y no hacer nada con él.

Enrutamiento de mensajes de ventana

A continuación se describen algunos temas más avanzados sobre las clases de MFC y el impacto que tienen en ellas el enrutamiento de mensajes de ventana y otros asuntos. La información aquí contenida es sucinta. Vea la referencia de la biblioteca de clases para obtener información detallada sobre las API públicas. Consulte el código fuente de la biblioteca de MFC para obtener más información sobre los detalles de implementación.

Vea la nota técnica 17 para obtener información detallada sobre la limpieza de ventanas, un tema muy importante en 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 en sí un objeto CWnd de C++, se llama primero 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 de CWnd predeterminada controla los siguientes mensajes y usa OnChildNotify para permitir que las ventanas secundarias (controles) accedan primero en el mensaje:

  • WM_MEASUREITEM y WM_DRAWITEM (para el dibujo automático)

  • WM_COMPAREITEM y WM_DELETEITEM (para el 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. Consulte a continuación para obtener más detalles sobre las barras de desplazamiento y los orígenes de los mensajes WM_HSCROLL y WM_VSCROLL.

Problemas de CFrameWnd

La clase CFrameWnd proporciona la mayor parte de la implementación de actualización de la interfaz de usuario y enrutamiento de comandos. Esto se usa principalmente con la ventana de marco principal de la aplicación (CWinApp::m_pMainWnd), pero se puede usar con cualquier ventana de marco.

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

La clase CFrameWnd proporciona la función de administración de la vista activa. Los mensajes siguientes se enrutan a través de la vista activa:

  • Todos los mensajes de comando (la vista activa tiene el acceso inicial a ellos)

  • Los mensajes WM_HSCROLL y WM_VSCROLL de las barras de desplazamiento del mismo nivel (vea abajo)

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

Problemas de CMDIFrameWnd/CMDIChildWnd

Las dos 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 consiste en que la ventana secundaria MDI activa tiene el acceso inicial a los comandos. Las funciones PreTranslateMessage predeterminadas controlan las tablas de aceleradores de las ventanas secundarias MDI (en primer lugar) y de las ventanas de marco MDI (en segundo lugar), así como los aceleradores estándar de comandos de sistema MDI que suelen administrarse mediante TranslateMDISysAccel (en último lugar).

Problemas de la barra de desplazamiento

Al controlar los mensajes de desplazamiento (WM_HSCROLL/OnHScroll y/o WM_VSCROLL/OnVScroll), es aconsejable intentar escribir el código del controlador para que no dependa de dónde procede el mensaje de la barra de desplazamiento. Este no es solo un problema general de Windows, ya que los mensajes de desplazamiento pueden provenir de controles de barra de desplazamiento genuinos o de barras de desplazamiento WS_HSCROLL/WS_VSCROLL, 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 en las barras de desplazamiento compartidas con ventanas divisoras. Vea 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 las barras de desplazamiento compartidas.

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 una vez completada la creación esto 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 se quiere tener un área MDICLIENT por la que poder desplazarse (como el Administrador de programas de Windows), asegúrese de establecer ambos estilos de barra de desplazamiento (WS_HSCROLL | WS_VSCROLL) en el estilo usado para crear CMDIFrameWnd.

En CSplitterWnd, los estilos de barra de desplazamiento se aplican a las barras de desplazamiento compartidas especiales de regiones divisoras. En las ventanas divisoras estáticas, normalmente no establecerá ninguno de estos dos estilos de barra de desplazamiento. En las ventanas divisoras dinámicas, el estilo de barra de desplazamiento se establecerá en la dirección en la que se va a dividir, es decir, WS_HSCROLL si se pueden dividir filas y WS_VSCROLL si se pueden dividir columnas.

Consulte también

Notas técnicas por número
Notas técnicas por categoría