[ contenidos | #winprog ]

Manejo de Mensajes

Ejemplo: window click

[images/window_click.gif]

Bien, tenemos una ventana pero esta no hace nada excepto lo que DefWindowProc( ) le permite que haga: cambiar el tamaño, ser maximizada, minimizada etc... Nada realmente excitante.

En la siguiente sección voy a mostrar como modificar lo que ya tenemos para que haga algo nuevo. Así, yo solo podría decirte "Procesa este mensaje y haz esto en él..." y tu sabrás lo que digo y serás capaz de hacerlo sin mirar un ejemplo entero. Esto es lo que espero, de todas maneras presta atención.

Ok, para comenzar tomemos el ejemplo de la última ventana en la que trabajamos y asegurémonos que compila y se ejecuta como lo esperamos. Puedes seguir trabajando en este archivo o puedes copiar el código a un nuevo proyecto.

Vamos a agregarle a la ventana la capacidad de mostarle al usuario cuál es el nombre del programa. ESto se hará cada vez que el usuario realice un click sobre la ventana. Nada excitante, es básicamente para ir entendiendo como manejar mensajes. Observemos que tenemos en nuestro WndProc():

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

Si queremos manipular clicks del mouse, necesitamos agregar un menejador (o procesador) WM_LBUTTONDOWN (o WM_RBUTTONDOWN, MBUTTONDOWN para clicks con el botón derecho y medio, respectivamente). Cuando decimos "procesar un mensaje" significa agregar dicho mensaje dentro del WinProc( ) de nuestra clase ventana, lo hacemos de la siguiente manera:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch(msg)
   {
      case WM_LBUTTONDOWN:    // <-
                              // <-     agregamos esto
      break;                  // <-
      case WM_CLOSE:
         DestroyWindow(hwnd);
      break;
      case WM_DESTROY:
         PostQuitMessage(0);
      break;
      default:
         return DefWindowProc(hwnd, msg, wParam, lParam);
   }
   return 0;
}

El orden en cual manejamos los mensajes raramente importa. Solo asegúrate de poner el "break" despues de cada uno. Como puedes ver necesitamos otro case dentro de nuestro switch(). Ahora queremos que algo suceda cuando llegamos a esta nueva parte de nuestro programa.

Primero presentaré el código que queremos agregar (el que le mostrará al usuario el nombre del archivo del programa) y luego lo integraré dentro del nuestro programa. Mas adelante, probablemente solo te muestre el código y te dejaré que lo integres tu mismo. Esto es, por su puesto, mejor para mi porque no tengo que andar tipeando demasiado y mejor para vos porque serás capaz de agregar el código dentro de CUALQUIER programa, no solamente el que estamos presentando. De todas maneras, si no estás seguro de como hacerlo puedes mirar el código fuente correspondiente a esta sección incluído en el archivo zip.

GetModuleFileName(hInstance, szFileName, MAX_PATH);
MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);

Este código no puede ser copiado en cualquier lugar dentro de nuestro programa. Especificamente queremos que éste se ejecute cuando el usuario clickea el mouse, por lo tanto veremos como poner dicho código dentro de nuestro programa:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_LBUTTONDOWN:
// Comienzo del nuevo código
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);

            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);
        }
// Fin del nuevo código
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

Observa el nuevo conjunto de llaves { }. Estas son necesarias cuando declaramos una variable dentro de una sentencia switch( ). Esto es conocimiento básico de C pero pienso que es bueno recordarlo para aquellos que están haciendo las cosas por el camino difícil.

Una vez que has agregado el código, compílalo. Si funciona, has click dentro de la ventana y verás desplegado un cuadro con el nombre del archivo .exe .

Habrás notado que hemos agregado dos variables, hInstance y szFileName. Observa la función GetModuleFileName y verás que el primer parámetro es un HINSTANCE refiriéndose al módulo ejecutable (nuestro programa,el archivo .exe). ¿Donde obtenemos eso? GetModuleHandle( ) es la respuesta. Las referencias de GetModuleHandle( ) indican que si pasamos NULL como parámetro, nos retornará un "Handle al archivo usado para crear el proceso de llamada" que es exactamente lo que necesitamos: el HINSTANCE anteriormente mencionado. Poniendo todo esto, finalizamos con la siguiente declaración:

HINSTANCE hInstance = GetModuleHandle(NULL);

Ahora, con el segundo parámetro, nuevamente usamos nuestro confiable manual de referencia. Este dice: "un puntero al buffer que recibe la ruta y el nombre de archivo del modulo especificado" y el tipo de dato es LPTSTR (o LPSTR si tu referencia es vieja). Debido a que LPSTR es equivalente a char* podemos declarar un arreglo de caracteres como el siguiente:

char szFileName[MAX_PATH];

MAX_PATH es una macro incluída vía <windows.h> definida como la máxima longitud de un buffer necesario para almacenar el nombre de un archivo bajo Win32. Pasamos MAX_PATH a la función GetModuleFileName( ) para que ésta conozca el tamaño del buffer.

Luego de que GetModuleFileName( ) es llamado, el búffer szFileName será llenado con un string terminado en cero conteniendo el nombre de nuestro archivo exe. Pasamos este valor a MessageBox() porque es una forma fácil de mostrarlo en pantalla.

Por lo tanto, si has agregado esto en el código, compílalo. Si funciona haz click en la ventana y deberás ver desplegado un mensaje con el nombre del archivo.

Si esto no funciona, aquí esta el código completo. Compáralo con el tuyo para encontrar que errores has cometido.

#include <windows.h>

const char g_szClassName[] = "myWindowClass";

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_LBUTTONDOWN:
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);

            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION);
        }
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

Copyright © 1998-2003, Brook Miles (theForger). All rights reserved.

Version en Español: Federico Pizarro - 2003.