3 Estructura de un programa Windows GUI

Hay algunas diferencias entre la estructura de un programa C/C++ normal, y la correspondiente a un programa Windows GUI. Algunas de estas diferencias se deben a que los programas GUI estás basados en mensajes, otros son sencillamente debidos a que siempre hay un determinado número de tareas que hay que realizar.

// Ficheros include:
#include <windows.h>

// Prototipos:
LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

// Función de entrada:
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpszCmdParam,
                   int nCmdShow)
{
   // Declaración:
   // Inicialización:
   // Bucle de mensajes:
   return Message.wParam;
}

// Definición de funciones:

Cabeceras

Lo primero es lo primero, para poder usar las funciones del API de Windows hay que incluir al menos un fichero de cabecera, pero generalmente no bastará con uno.

El fichero <windows.h> lo que hace es incluir la mayoría de los ficheros de cabecera corrientes en aplicaciones GUI, pero podemos incluir sólo los que necesitemos, siempre que sepamos cuales son. Por ejemplo, la función WinMain está declarada en el fichero de cabecera winbase.h.

Generalmente esto resultará incómodo, ya que para cada nueva función, mensaje o estructura tendremos que comprobar, y si es necesario, incluir nuevos ficheros. Es mejor usar windows.h directamente.

Prototipos

Cada tipo (o clase) de ventana que usemos en nuestro programa (normalmente sólo será una), o cada cuadro de diálogo (de estos puede haber muchos), necesitará un procedimiento propio, que deberemos declarar y definir. Siguiendo la estructura de un programa C, esta es la zona normal de declaración de prototipos.

Función de entrada, WinMain

La función de entrada de un programa Windows es "WinMain", en lugar de la conocida "main". Normalmente, la definición de esta función cambia muy poco de una aplicaciones a otras. Se divide en tres partes claramente diferenciadas: declaración, inicialización y bucle de mensajes.

Parámetros de entrada de "WinMain"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  LPSTR lpszCmdParam, int nCmdShow)

La función WinMain tiene cuatro parámetros de entrada:

  • hInstance es un manipulador para la instancia del programa que estamos ejecutando. Cada vez que se ejecuta una aplicación, Windows crea una Instancia para ella, y le pasa un manipulador de dicha instancia a la aplicación.
  • hPrevInstance es un manipulador a instancias previas de la misma aplicación. Como Windows es multitarea, pueden existir varias versiones de la misma aplicación ejecutándose, varias instancias. En Windows 3.1, este parámetro nos servía para saber si nuestra aplicación ya se estaba ejecutando, y de ese modo se podían compartir los datos comunes a todas las instancias. Pero eso era antes, ya que en Win32 usa un segmento distinto para cada instancia y este parámetro es siempre NULL, sólo se conserva por motivos de compatibilidad.
  • lpszCmdParam, esta cadena contiene los argumentos de entrada del comando de línea.
  • nCmdShow, este parámetro especifica cómo se mostrará la ventana. Para ver sus posibles valores consultar valores de ncmdshow. Se recomienda no usar este parámetro en la función ShowWindow la primera vez que se ésta es llamada. En su lugar debe usarse el valor SW_SHOWDEFAULT.

Función WinMain típica

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpszCmdParam, int nCmdShow)
{
   /* Declaración: */
   HWND hwnd;
   MSG mensaje;
   WNDCLASSEX wincl;

   /* Inicialización: */
   /* Estructura de la ventana */
   wincl.hInstance = hInstance;
   wincl.lpszClassName = "NUESTRA_CLASE";
   wincl.lpfnWndProc = WindowProcedure;
   wincl.style = CS_DBLCLKS;
   wincl.cbSize = sizeof(WNDCLASSEX);

   /* Usar icono y puntero por defecto */
   wincl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
   wincl.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
   wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
   wincl.lpszMenuName = NULL;
   wincl.cbClsExtra = 0;
   wincl.cbWndExtra = 0;
   wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND;

   /* Registrar la clase de ventana, si falla, salir del programa */
   if(!RegisterClassEx(&wincl)) return 0;

   hwnd = CreateWindowEx(
           0,
           "NUESTRA_CLASE",
           "Ejemplo 001",
           WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT,
           CW_USEDEFAULT,
           544,
           375,
           HWND_DESKTOP,
           NULL,
           hThisInstance,
           NULL
   );

   ShowWindow(hwnd, SW_SHOWDEFAULT);

   /* Bucle de mensajes: */
   while(TRUE == GetMessage(&mensaje, 0, 0, 0))
   {
      TranslateMessage(&mensaje);
      DispatchMessage(&mensaje);
   }

   return mensaje.wParam;
}

Declaración

En la primera zona declararemos las variables que necesitamos para nuestra función WinMain, que como mínimo serán tres:

  • HWND hWnd, un manipulador para la ventana principal de la aplicación. Ya sabemos que nuestra aplicación necesitará al menos una ventana.
  • MSG Message, una variable para manipular los mensajes que lleguen a nuestra aplicación.
  • WNDCLASSEX wincl, una estructura que se usará para registrar la clase particular de ventana que usaremos en nuestra aplicación. Existe otra estructura para registrar clases que se usaba antiguamente, pero que ha sido desplazada por esta nueva versión, se trata de WNDCLASS.

Inicialización

Esta zona se encarga de registrar la clase o clases de ventana, crear la ventana y visualizarla en pantalla.

Para registrar la clase primero hay que rellenar adecuadamente la estructura WNDCLASSEX, que define algunas características que serán comunes a todas las ventanas de una misma clase, como color de fondo, icono, menú por defecto, el procedimiento de ventana, etc. Después hay que llamar a la función RegisterClassEx.

En el caso de usar una estructura WNDCLASS se debe registrar la clase usando la función RegisterClass.

A continuación se crea la ventana usando la función CreateWindowEx, la función CreateWindow ha caído prácticamente en desuso. Cualquiera de estas dos funciones nos devuelve un manipulador de ventana que podemos necesitar en otras funciones, sin ir más lejos, la siguiente.

Pero esto no muestra la ventana en la pantalla. Para que la ventana sea visible hay que llamar a la función ShowWindow. La primera vez que se llama a ésta función, después de crear la ventana, se puede usar el parámetro nCmdShow de WinMain como parámetro o mejor aún, como se recomienda por Windows, el valor SW_SHOWDEFAULT.

Bucle de mensajes

Este es el núcleo de la aplicación, como se ve en el ejemplo el programa permanece en este bucle mientras la función GetMessage retorne con un valor TRUE.

while(TRUE == GetMessage(&mensaje, 0, 0, 0)) {
   TranslateMessage(&mensaje);
   DispatchMessage(&mensaje);
}

Este es el bucle de mensajes recomendable, aunque no sea el que se usa habitualmente. La razón es que la función GetMessage puede retornar tres valores: TRUE, FALSE ó -1. El valor -1 indica un error, así que en este caso se debería abandonar el bucle.

El bucle de mensajes que encontraremos habitualmente es este:

while(GetMessage(&mensajee, 0, 0, 0)) {
   TranslateMessage(&mensaje);
   DispatchMessage(&mensaje);
}
Nota:

El problema con este bucle es que si GetMessage regresa con un valor -1, que indica un error, la condición del "while" se considera verdadera, y el bucle continúa. Si el error es permanente, el programa jamás terminará.

La función TranslateMessage se usa para traducir los mensajes de teclas virtuales a mensajes de carácter. Veremos esto con más detalle en el capítulo dedicado al teclado (cap. 34).

Los mensajes traducidos se reenvían a la lista de mensajes del proceso, y se recuperarán con las siguientes llamadas a GetMessage.

La función DispatchMessage envía el mensaje al procedimiento de ventana, donde será tratado adecuadamente. El próximo capítulo está dedicado al procedimiento de ventana, y al final de él estaremos en disposición de crear nuestro primer programa Windows.

Definición de funciones

En esta parte definiremos, entre otras cosas, los procedimientos de ventana, que se encargan de procesar los mensajes que lleguen a cada ventana.