Compartir a través de


Mensajes de ventana (Introducción a Win32 y C++)

Una aplicación GUI debe responder a los eventos del usuario y del sistema operativo.

  • Eventos del usuario son aquellas formas en que alguien puede interactuar con el programa: clics del ratón, pulsaciones de teclas, gestos en la pantalla táctil, etc.
  • Eventos del sistema operativo son todo aquello "fuera" del programa que pueda afectar al funcionamiento de este. Por ejemplo, el usuario podría conectar un nuevo dispositivo de hardware o Windows podría entrar en un estado de ahorro de consumo de energía (suspensión o hibernación).

Estos eventos pueden producirse en cualquier momento mientras se ejecuta el programa, en casi cualquier orden. ¿Cómo estructuraría un programa cuyo flujo de ejecución no se puede predecir de antemano?

Para solucionar este problema, Windows usa un modelo de paso de mensajes. El sistema operativo se comunica con la ventana de la aplicación pasándole mensajes a esta. Un mensaje es solo un código numérico que designa un evento determinado. Por ejemplo, si el usuario pulsa el botón izquierdo del ratón, la ventana recibe un mensaje que tiene el código de mensaje siguiente.

#define WM_LBUTTONDOWN    0x0201

Algunos mensajes tienen datos asociados a ellos. Por ejemplo, el mensaje WM_LBUTTONDOWN incluye la coordenada x y la coordenada y del cursor del ratón.

Para pasar un mensaje a una ventana, el sistema operativo llama al procedimiento de ventana registrado para esa ventana. (Esto es para lo que sirve el procedimiento de ventana).

El bucle de mensajes

Una aplicación recibirá miles de mensajes mientras se ejecuta. (Tenga en cuenta que cada pulsación de tecla y clic del botón del ratón genera un mensaje). Además, una aplicación puede tener varias ventanas, cada una con su propio procedimiento de ventana. ¿Cómo recibe el programa todos estos mensajes y los manda al procedimiento de ventana correcto? La aplicación necesita un bucle para recuperar los mensajes y enviarlos a las ventanas correctas.

De cada subproceso que crea una ventana, el sistema operativo crea una cola para los mensajes de ventana. Esta cola incluye mensajes de todas las ventanas que se crean en ese subproceso. La cola en sí queda oculta del programa. No se puede manipular la cola directamente. Sin embargo, puede extraer un mensaje de la cola llamando a la función GetMessage.

MSG msg;
GetMessage(&msg, NULL, 0, 0);

Esta función quita el primer mensaje del encabezado de la cola. Si la cola está vacía, la función se bloquea hasta que se pone en cola otro mensaje. El hecho de que GetMessage se bloquee no hará que su programa no responda. Si no hay ningún mensaje, el programa no hará nada. Si tiene que realizar el procesamiento en segundo plano, puede crear subprocesos adicionales que sigan ejecutándose mientras GetMessage espera otro mensaje. (Consulte Evitar cuellos de botella en el procedimiento de ventana).

El primer parámetro de GetMessage es la dirección de una estructura MSG. Si la función se realiza correctamente, esta rellena la estructura MSG con información sobre el mensaje. Esto incluye la ventana de destino y el código del mensaje. Los otros tres parámetros permiten filtrar los mensajes que obtiene de la cola. En casi todos los casos, pondrá estos parámetros a cero.

Aunque la estructura MSG contiene información sobre el mensaje, casi nunca se examinará esta estructura directamente. En su lugar, se pasará directamente a otras dos funciones.

TranslateMessage(&msg); 
DispatchMessage(&msg);

La función TranslateMessage está relacionada con la entrada del teclado. Convierte las pulsaciones de tecla (tecla de dirección abajo, tecla de dirección arriba) en caracteres. No hay que saber cómo funciona esta función; solo debe acordarse de llamarla antes de DispatchMessage.

La función DispatchMessage informa al sistema operativo para que llame al procedimiento de ventana de la ventana que es el destino del mensaje. En otras palabras, el sistema operativo busca el identificador de ventana en la tabla de ventanas, busca el puntero de la función asociado a la ventana e invoca la función.

Por ejemplo, supongamos que el usuario pulsa el botón izquierdo del ratón. Esto provoca una cadena de eventos:

  1. El sistema operativo coloca un mensaje WM_LBUTTONDOWN en la cola de mensajes.
  2. El programa llama a la función GetMessage.
  3. GetMessage extrae el mensaje WM_LBUTTONDOWN de la cola y rellena la estructura MSG.
  4. El programa llama a las funciones TranslateMessage y DispatchMessage.
  5. Dentro de DispatchMessage, el sistema operativo llama al procedimiento de ventana.
  6. El procedimiento de ventana puede responder al mensaje o ignorarlo.

Cuando se devuelve el procedimiento de ventana, se vuelve a DispatchMessage. Esto hace que se regrese al bucle de mensajes para el siguiente mensaje. Siempre que el programa se esté ejecutando, los mensajes seguirán llegando a la cola. Por ello, debe tener un bucle que extraiga continuamente los mensajes de la cola y los envíe. Puede considerar que el bucle hace lo siguiente:

// WARNING: Don't actually write your loop this way.

while (1)      
{
    GetMessage(&msg, NULL, 0,  0);
    TranslateMessage(&msg); 
    DispatchMessage(&msg);
}

Tal cual como se escribió, por supuesto, este bucle nunca terminaría. Aquí es donde entra el valor devuelto de la función GetMessage. Normalmente, GetMessage devuelve un valor distinto de cero. Cuando quiera salir de la aplicación y salir del bucle de mensajes, llame a la función PostQuitMessage.

        PostQuitMessage(0);

La función PostQuitMessage coloca un mensaje WM_QUIT en la cola de mensajes. WM_QUIT es un mensaje especial: hace que GetMessage devuelva cero, lo que marca el final del bucle de mensajes. Este es el bucle de mensajes revisado.

// Correct.

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Siempre que GetMessage devuelva un valor distinto de cero, la expresión del bucle while se evalúa como true. Después de llamar a PostQuitMessage, la expresión pasa a false y el programa se interrumpe en el bucle. (Un resultado interesante de esta operación es que el procedimiento de ventana nunca recibe un mensaje WM_QUIT. Por lo tanto, no es necesario tener una instrucción case para este mensaje en el procedimiento de ventana).

La siguiente pregunta obvia es cuándo llamar a PostQuitMessage. Volveremos a esta pregunta en el tema Cerrar la ventana, pero primero tenemos que escribir nuestro procedimiento de ventana.

Mensajes publicados frente a mensajes enviados

En la sección anterior se ha hablado sobre los mensajes que se ponen en cola. A veces, el sistema operativo llamará directamente a un procedimiento de ventana y pasará la cola.

La terminología de esta distinción puede ser confusa:

  • Publicar un mensaje significa que el mensaje pasa a la cola de mensajes y se envía a través del bucle de mensajes (GetMessage y DispatchMessage).
  • Enviar un mensaje significa que el mensaje se salta la cola y el sistema operativo llama directamente al procedimiento de ventana.

Por ahora, la diferencia no es muy importante. El procedimiento de ventana controla todos los mensajes. Sin embargo, algunos mensajes se saltan la cola y pasan directamente al procedimiento de ventana. Sin embargo, puede hacer algo diferente si la aplicación se comunica entre ventanas. Puede encontrar una explicación más detallada sobre este problema en el tema Acerca de los mensajes y las colas de mensajes.

Siguientes

Escribir el procedimiento de ventana