Ejemplo: simple window
A veces las personas ingresan al IRC y preguntan "¿Como hago una ventana...?". Bien, no es tan simple como parece. No es difícil una vez que sepas lo que estás haciendo, pero hay algunas cosas que necesitas hacer para poder crear un ventana. Y son mas de las que pueden ser explicadas en una sala de chat o en una nota rápida.Siempre me gustó hacer las cosas primero y aprenderlas luego... por lo tanto aqui está el código de una simple ventana que será explicado en breve.
#include <windows.h> const char g_szClassName[] = "myWindowClass"; // Step 4: the Window Procedure 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; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hwnd; MSG Msg; //Step 1: Registering the Window Class 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; } // Step 2: Creating the Window 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); // Step 3: The Message Loop while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; }
Generalmente este es el programa más simple que puedes escribir par crear una ventana funcional, digamos unas 70 líneas de código. Si has compilado el primer ejemplo, entonces este debería funcionar sin problemas.
La Clase Ventana no tiene nada que ver con las clases de C++.
const char g_szClassName[] = "myWindowClass";La variable anterior almacena el nombre de nuestra Clase Ventana y la usaremos en breve para registrar nuestra clase en el sistema.
WNDCLASSEX wc; 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; }
Este es el código que usamos en WinMain( )
para registrar
nuestra Clase Ventana en el sistema. Para ello, rellenamos los campos de
una estructura WNDCLASSEX
y llamamos a
RegisterClassEx( )
. Los campos de la estructura son los
siguientes:
cbSize
style
lpfnWndProc
cbClsExtra
cbWndExtra
hInstance
hIcon
hCursor
hbrBackground
lpszMenuName
lpszClassName
hIconSm
Luego llamamos a RegisterClassEx( ) y chequeamos por alguna falla, si hubo alguna mostramos un mensaje y abortamos el programa retornando a la función WinMain( ).
Una vez que la clase ha sido registrada, podemos usarla para crear una ventana.
Observemos los parámetros de la función CreateWindowEx( )
(como lo haremos con cada nueva llamada a la API que veamos), los
explicaré brevemente:
HWND hwnd;
hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, g_szClassName, "The title of my window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL);
El
primer parámetro (WS_EX_CLIENTEDGE
) es el estilo extendido
de la ventana, en este caso he configurado éste para darle un borde
interno y hundido al marco de la ventana. Pon este valor en cero para
observar la diferencia, o prueba con otros valores para ver que hacen.
A continuacióm tenemos el nombre de la clase
(g_szClassName
), éste le dice al sistema que tipo de
ventana crear. Debido a que queremos crear una ventana a partir de la
clase que hemos registrado, usamos el nombre de dicha clase. Luego
especificamos el nombre de nuestra ventana o título que aparecerá en la
barra de título de la ventana.
El parámetro que tenemos como
WS_OVERLAPPEDWINDOW
es estilo de la ventana. Hay
algunos de éstos y deberías mirarlos y experimentar para observar que
hacen. De todos modos serán cubietos mas adelante.
Los siguientes
cuatro parámetros ( CW_USEDEFAULT, CW_USEDEFAULT,320,240)
son las coordenadas X e Y de la esquina superior izquierda de la ventana
y el alto y ancho de de la ventana, respectivamente. He puesto los
campos X e Y con el valor CW_USEDEFALT
para dejar que
windows elija en qué lugar de la pantalla ubicar la ventana. Recuerda
que la parte izquierda de la pantalla tiene un valor de X igual a cero y se
incrementa hacia la derecha y la parte superior de la pantalla tiene un
valor cero de Y y se incrementa cuando avanzamos hacia abajo en la
pantalla. Las unidades son los pixeles, el cual es la miníma unidad que
una pantalla puede mostrar en una resolución determinada.
Los siguientes
parámetros (NULL,NULL,g_hInstance,NULL
) corresponden al
handle de la ventana padre, al handle del menú, al handle de la
instancia de la aplicación y un puntero a los datoscon los cuales se creó la
ventana. En windows las ventanas son ordenadas en la pantalla de acuerdo
a una jerarquía de padre e hijo. Cuando ves un botón en una ventana, el
botón es el Hijo de la ventana en la cual está contenido y dicha
ventana es su Padre.
En este ejemplo, el handle al padre es nulo debido a que no tenemos padre,
estamos en el nivel principal de las ventanas. El handle al menú es nulo
debido a que, por ahora, no tenemos menú. El handle a la instancia de la
aplicación es puesto con el valor que es pasado en el primer
parámetro del WinMain( )
. Los datos de creación (los culaes
nunca uso) pueden ser usados para enviar información adicional a la
ventana que está siendo creada. También es nulo. Si estás preguntándote
que es la palabra mágica NULL
, es simplemente definida como cero (0
).
En realidad, en C está definida como ((void *) 0)
, debido a que está
destinada a ser usada con punteros. Por lo tanto probablemente obtengas
algunos "warnings" si la utilizas para valores enteros, de todas maneras
esto depende del compilador y del nivel de warnings en la configuaración
del mismo. Puedes elegir ignorar los warnings o usar el valor cero (0
)en
lugar de NULL.
La principal causa de que las personas no conozcan cuales
son las fallas de sus programas es que no chequean los valores de
retorno de las llamadas a funciones para determinar si éstas han fallado
o no. CreateWindow( )
puede fallar aún si eres un experimentado
programador debido a que hay una gran cantidad de errores que son
fáciles de cometer. Hasta que aprendas como identificar rápidamente
dichos errores date la oportunidad de resolverlos y siempre chequea los
valores de retorno!
if(hwnd == NULL) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; }
Después que hemos creado la
ventana y hemos chequeado para asegurarnos de que tengamos un handle
válido, mostramos la ventana utilizando el último parámetro provisto en
WinMain( )
y luego lo actualizamos para asegurarnos que se halla redibujado en la pantalla a si mismo de manera apropiada.
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
El parámetro nCmdShow
es opcional, simplemente puedes pasar el valor SW_SHOWNORMAL
en todo momento y listo. Sin embargo si usamos el parámetro provisto en WinMain( )
le da a quien esté corriendo tu programa la oportunidad de especificar si quiere que la ventana comienze visible, maximizada,
minimizada, etc... Encontrarás opciones para este parámetro en las
propiedades de los iconos del escritorio y apartir de estos valores será
determinado el valor que tome el parámetro nCmdShow.
Este es el corazón del programa, la mayoria de las cosas que tu programa realiza pasan a través de este punto de control.
while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam;
GetMessage()
obtiene un mensaje de la cola de mensajes de tu
aplicación. Cada vez que el usuario mueve el mouse, presiona el teclado,
hace click en el menú de la ventana , etc... el sistema genera mensajes
que ingresan en la cola de mensaje de la tu aplicación. Utilizando
GetMessage( )
estás solicitando que el siguiente mensaje disponible en
la cola sea removido de la misma y te sea entregado para procesarlo. Si
no hay mensajes, GetMessage( )
se bloquea . Si no estás familiarizado con dicho término, esto significa que espera a que halla un mensaje en la
cola y luego te lo retorna.
TranslateMessage( )
hace algunos procesos
adicionales en los eventos del teclado, como generar mensajes WM_CHAR
que van junto a los mensajes WM_KEYDOWN
. Finalmente DispatchMessage( )
envía el mensaje a la ventana que había recibido dicho mensaje. Esta
podría ser nuestra ventana principal, otra ventana, o un control e
incluso una ventana que fué creada "detrás de la escena" por el sistema
o algún otro progama. Esto no es algo que debería precocuparte debido a
que lo único que nos interesa saber es que obtenemos el mensaje y lo
envíamos, el sistema se encarga de hacer el resto asegurándose de
trabajar con la ventana correcta.
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; }
El Windows Procedure (pronunciado "winprock") es llamado por cada mensaje que
procesamos, el parámetro HWND
es el handle dela ventana a la cual
se aplica el mensaje. Esto es importante debido a que puedes tener dos o
mas ventanas de la misma clase que usarán el mismo Windows Procedure
(WndProc( )
), la diferencia es que el parámetro hWnd
será diferente dependiedno de la ventana. Por ejemplo cuando obtenemos el mensaje
WM_CLOSE
(cerrar ventana) destruimos la ventana y debido a que usamos el
handle de la ventana (que recibimos en el primer parámetro) sabremos que
ventana es la que hay que destruir. Las demás ventanas no se verán
afectadas, solo la que recibió el mensaje.
WM_CLOSE
es envíado cuando el
usuario presiona ALT+F4 o cuando presiona el botón . Esto causará que
la ventana sea destruida por default, pero prefiero manejarlo
explicitamente debido a que es el punto perfecto para realizar
"limpiezas" o preguntarle al usuario si desea guardar los cambios,
etc...antes que la ventana se destruida. (i.e el programa finalice).
Cuando llamamos a DestroyWindow( )
el sistema envía el mensaje
WM_DESTROY
a la ventana que va a ser destruída, que en este caso es
nuestra ventana, y por lo tanto destruimos todas las ventanas hijas
antes de remover nuestra ventana del sistema. Debido a que esta es
nuestra única ventana en nuestro programa todo lo que hacemos es salir
del mismo, para ello utilizamos la llamada PostQuitMessage( )
. Esta
envía el mensaje WM_QUIT
al bucle de mensajes pero nunca recibimos este
mensaje debido a que éste causa que GetMessage( )
retorne FALSE
y como vimos en el código del Bucle de Mensajes, cuando esto sucede, dejamos de
procesar mensajes y retornamos el código resultante final, el Wparam
del WM_QUIT
el cual es el valor pasado en PostQuitMessage( )
. El valor de retorno solo es usado si tu programa está diseñado para
ser llamado por otro programa y quieres que retorne un valor especifico.
Bien, eso eso fué todo!(por ahora). Si las cosas aún no se han entendido del todo, solo sigue adelante y espera que las cosas se tornen mas claras, como cuando veamos algunos programas.
Versión en Español Federico Pizarro - 2003