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

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

  • Los eventos del usuario incluyen todas las formas en que alguien puede interactuar con el programa: clics del mouse, trazos clave, gestos de pantalla táctil, etc.
  • Los eventos del sistema operativo incluyen cualquier cosa "fuera" del programa que pueda afectar a cómo se comporta el programa. Por ejemplo, el usuario podría conectar un nuevo dispositivo de hardware o Windows podría entrar en un estado de menor 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 estructura 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 pasando mensajes a ella. Un mensaje es simplemente un código numérico que designa un evento determinado. Por ejemplo, si el usuario presiona el botón izquierdo del mouse, la ventana recibe un mensaje que tiene el siguiente código de mensaje.

#define WM_LBUTTONDOWN    0x0201

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

Para pasar un mensaje a una ventana, el sistema operativo llama al procedimiento de ventana registrado para esa ventana. (Y ahora sabe para qué 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 mouse 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 entrega al procedimiento de ventana correcto? La aplicación necesita un bucle para recuperar los mensajes y enviarlos a las ventanas correctas.

Para cada subproceso que crea una ventana, el sistema operativo crea una cola para los mensajes de ventana. Esta cola contiene mensajes para todas las ventanas que se crean en ese subproceso. La cola en sí está 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 los bloques GetMessage no respondan al programa. Si no hay ningún mensaje, no hay nada que haga el programa. Si tiene que realizar el procesamiento en segundo plano, puede crear subprocesos adicionales que sigan ejecutándose mientras GetMessage espera otro mensaje. (Vea 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 ejecuta correctamente, 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, establecerá estos parámetros en cero.

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

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

La función TranslateMessage está relacionada con la entrada del teclado. Convierte las pulsaciones de tecla (tecla abajo, tecla arriba) en caracteres. Realmente no tiene que saber cómo funciona esta función; recuerde llamarlo antes de DispatchMessage. El vínculo a la documentación de MSDN le proporcionará más información, si tiene curiosidad.

La función DispatchMessage indica al sistema operativo 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 su tabla de ventanas, busca el puntero de función asociado a la ventana e invoca la función .

Por ejemplo, supongamos que el usuario presiona el botón izquierdo del mouse. 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 de 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, vuelve a DispatchMessage. Esto devuelve al bucle de mensajes para el siguiente mensaje. Siempre que el programa se esté ejecutando, los mensajes seguirán llegando a la cola. Por lo tanto, debe tener un bucle que extraiga continuamente los mensajes de la cola y los envíe. Puede pensar en el bucle de la siguiente manera:

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

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

Como se escribió, por supuesto, este bucle nunca finalizarí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 indica 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 se convierte en false y el programa se interrumpe del bucle. (Un resultado interesante de este comportamiento 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 habló sobre los mensajes que van a una cola. A veces, el sistema operativo llamará directamente a un procedimiento de ventana, pasando 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 omite 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 omiten la cola y pasan directamente al procedimiento de ventana. Sin embargo, puede marcar una diferencia si la aplicación se comunica entre ventanas. Puede encontrar una explicación más exhaustiva de este problema en el tema Acerca de mensajes y colas de mensajes.

Siguientes

Escritura del procedimiento de ventana