Anda di halaman 1dari 60

Introduccin

Sobre qu trata este tutorial?


Este tutorial est destinado a presentarte las bases (y algunos extras comunes) de
escribir programas utilizando la API de WIN32. El lneguaje usado es C, y generalmente
compilarn bien la mayora de los compiladores de C++. Adems, mucha de la
informacin es aplicable a cualquier lenguaje que pueda acceder a la API, incluyendo
Java, Assembler y Visual Basic. Sin embargo no voy a presentar nign cdigo
relacionado a estos lenguajes y queda a cargo del lector si desea hacerlo, de hecho,
varias personas han usado previamente este tutorial en otros lenguajes con algo de
xito.
Este tutorial no pretende ensear el lenguaje C++ ni tampoco cmo usar algn
compilador en particular (Borland C++, Visual C++, BCC-Win32, etc...). Sin embargo
me tomar un momento en el apndice para dar algunas notas sobre como usar los
compiladores que conozco.
Si no sabes que es una macro o que que es typedef o si no sabes cmo funciona la
sentecia switch(), entonces te recomiendo que primero leas un buen libro o tutorial
sobre el lenguaje C.

Notas Importantes:
A veces a lo largo del texto voy a indicar ciertas cosas que son IMPORTANTES de leer,
por lo tanto debes prestarle atencin. La primer nota es:
Los fuentes includos en el archivo zip no son opcionales! No he incluido todo el
cdigo en el tutorial, solo aquel que es relevante para lo que estoy explicando en ese
momento. Para ver como este cdigo se integra con el resto del programa debes mirar
el codigo fuente incluido en el archivo zip.
Y segundo:
Leelo todo! Si tienes alguna pregunta durante una seccin del tutorial, solo ten un poco
de paciencia ya que esta podra ser respondida en el resto del mismo.
Otra cosa para recordar es que si tienes alguna pregunta sobre un tema A sta podra
finalmente ser respondida en una discusin de B o C o quizs D. Por lo tanto si tienes
alguna duda sigue leyendo el tutorial.
OK, Pienso que esto son todos los consejos que tengo que dar por el momento.
Adelante...

Un Simple programa Win32

Si eres un completo principiante vamos a asegurarnos que eres capaz de compilar una
aplicacin bsica de windows. Copia el siguiente cdigo a tu compilador y si todo va
bien obtendrs uno de los programas mas pobres jams escritos.
Recuerda compilar esto como C, no como C++. Esto probablemente no importe pero
debido a que todo el cdigo es C puro tiene sentido comenzar por el camino correcto.
En muchos casos todo lo necesario es simplemente poner el cdigo en un archivo con
extensin .c en vez de .cpp. Si todo esto te marea simplemente guarda el archivo con
el nombre test.c y listo.

#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MessageBox(NULL, "Goodbye, cruel world!", "Note", MB_OK);
return 0;
}

Si esto no funciona, tu primer paso es leer que errores obtuviste y si no los entiendes
bscalos en la ayuda o en algn documento que acompae tu compilador. Asegrate
de haber especificado "GUI Win32" y no "Console" dentro de la opcin
project/compile/target de tu compilador. Desafortunadamente no puedo ayudarte
mucho con esta parte ya que los errores y cmo corrregirlos varan de compilador en
compilador (y de persona en persona).
Quizs obtengas algunos "Warnings" dicindote que no has usado los parmetros
provistos en WinMain( ), pero esto est bien. Ahora que sabemos que puedes
compilar un programa veamos esta pequea porcin de cdigo:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow)
WinMain( ) es en Windows el equivalente al main( ) de DOS o UNIX. Es ah donde los
programas comienzan su ejecucin. Los parmetros son los siguientes:
HINSTANCE hInstance
Handle al mdulo ejecutable del programa (el archivo .exe en memoria).
HINSTANCE hPrevInstance
Siempre nulo (o NULL) en programas Win32.
LPSTR lpCmdLine
Un string (o cadena de caracteres) con los argumentos de linea de comando. No
incluye el nombre del programa.
int nCmdShow
Un valor entero que puede ser pasado a ShowWindow( ), veremos esto mas
adelante.
hInstance es usado para cosas como cargar recursos y cualquier otra tarea que sea
realizada en una base por-modulos. Un mdulo puede ser el archivo .EXE cargado en
memoria o una librera DLL cargada en tu programa. En la mayor parte de este tutorial
(por no decir todo el tutorial) habr un solo mdulo del cual preocuparse: el EXE.

hPrevInstance es usado en Win16 como handle a la instancia de ejecucin anterior (si


la hubo). Esto ya no se aplica en Win32 por lo tanto ignoramos este parmetro.

Especificaciones de llamada:
WINAPI especifica una convencin de llamada y es definida como stdcall. No te
preocupes si no sabes que sigifica esto ya que no nos afectar dentro del alcance de
este tutorial. Solo recurdalo.

Tipos de datos en Win32


Seguramente has encontrado que muchos de los tipos de datos tienen definiciones
especficas de windows, UINT para unsigned int (entero no
signado) LPSTR para char*, etc...lo que elijas depende de ti. Si te sientes mas cmodo
utilizando char* en lugar de LPSTR eres libre de hacerlo. Solo asegrate bien de que
tipo de dato se trata antes de substituir algo.
Slo recuerda algunas cosas y luego ser fcil de interpretar. El
prefijo LP representa Long Pointer (Puntero Largo). En Win32 la parte larga es obsoleta
por lo tanto no te preocupes por ella. Si no conoces que es un puntero puedes hacer lo
siguiente: 1) Buscar un tutorial de C, o 2) Seguir adelante y saltearte un parte.
Realmente recomiendo la primer opcin, aunque muchas personas elijen la segunda...
Despus no digas que no te avis.
La letra C que sigue luego de LP indica que se trata de un puntero "Constante". Por lo
tanto LPCSTR representa "Long Pointer to Const String" o "Puntero Largo un String
Constante" pero como ya dijimos, en Win32 podemos ignorar la parte "long" as que
esto podra leerse "Puntero a un String Constante", uno que no se puede modificar. Por
otro lado LPSTR no es constante y puede ser modificado.
Quizs has visto una "T" ah en medio. No te preocupes por esto por ahora, a menos
que intencionalmente ests trabajando con UNICODE, no significa nada.

Una Simple Ventana.


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 difcil una vez que sepas lo que ests 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 rpida.
Siempre me gust hacer las cosas primero y
aprenderlas luego... por lo tanto aqui est el cdigo
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 ms simple que puedes escribir par crear una
ventana funcional, digamos unas 70 lneas de cdigo. Si has compilado el primer
ejemplo, entonces este debera funcionar sin problemas.

Paso 1: Registrar la Clase Ventana


Una Clase Ventana almacena informacin sobre el tipo de ventana, incluyendo
su Window Procedure, los conos grandes y pequeos y el color de fondo. De esta
forma podemos registrar una clase una vez y luego crear muchas ventanas a partir de
sta, sin tener que especificar todos los atributos una y otra vez. Muchos de los
atributos que especificamos en la Clase Ventana pueden ser cambiados, si se desea, en
una base per-windows.
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 cdigo 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

El tamao de la estructura

style

El estilo de la clase (CS *), no confundirse con el estilo de la ventana (WS *).
Generalmente este campo puede ser puesto en cero.
lpfnWndProc
Puntero al Window Procedure de esta clase.
cbClsExtra
Cantidad extra de asignacin de memoria para datos de la clase. Generalmente
cero.
cbWndExtra
Cantidad extra de asignacin de memoria por ventana de este tipo.
Generalmente cero.
hInstance
Handle a la instancia de la aplicacin (la que obtenemos en el primer parmetro
de WinMain( ) )
hIcon
Handle al cono grande (32x32), mostrado cuando el usuario presiona Alt+Tab.
hCursor
Handle al cursor que ser mostrado sobre la ventana.
hbrBackground
Pincel para fijar el color de fondo de nuestra ventana.
lpszMenuName
Nombre del recurso Men para usar con las ventanas de esta clase.
lpszClassName
Nombre para identificar la clase.
hIconSm
Handle al cono pequeo (16x16), usado en la barra de tareas y en la esquina
superior izquierda de la ventana.
No te preocupes si esto no queda muy claro an, las distintas partes que vimos sern
explicadas mas adelante. Otra cosa para recordar es no intentar memorizar toda esta
estructura. Yo raramente (de hecho nunca) memorizo estructuras o parmetros de
funciones, esto es un desperdicio de tiempo y esfuerzo. Si conoces las funciones que
necesitas llamar, entoces es una cuestin de segundos mirar los parmetros exactos en
la ayuda de tu compilador. Si no tienes los archivos de ayuda entonces consigue
algunos porque sin ellos estas perdido. Eventualmente irs aprendiendo los parmetros
de las funciones que mas usamos.

Luego llamamos a RegisterClassEx( ) y chequeamos por alguna falla, si hubo alguna


mostramos un mensaje y abortamos el programa retornando a la funcin WinMain( ).

Paso 2: Crear la Ventana:


Una vez que la clase ha sido registrada, podemos usarla para crear una ventana.
Observemos los parmetros de la funcin 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 parmetro (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 continuacim 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 ttulo que aparecer en la barra de
ttulo de la ventana.
El parmetro que tenemos como WS_OVERLAPPEDWINDOW es estilo de la ventana. Hay
algunos de stos y deberas mirarlos y experimentar para observar que hacen. De
todos modos sern cubietos mas adelante.
Los siguientes cuatro parmetros ( 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 minma unidad que una pantalla puede mostrar en una
resolucin determinada.
Los siguientes parmetros (NULL,NULL,g_hInstance,NULL) corresponden al handle de
la ventana padre, al handle del men, al handle de la instancia de la aplicacin 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 jerarqua de padre e hijo. Cuando ves un
botn en una ventana, el botn 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
aplicacin es puesto con el valor que es pasado en el primer parmetro
del WinMain( ). Los datos de creacin (los culaes nunca uso) pueden ser usados para
enviar informacin adicional a la ventana que est siendo creada. Tambin es nulo. Si
ests preguntndote que es la palabra mgica 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 configuaracin 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 an si eres un
experimentado programador debido a que hay una gran cantidad de errores que son
fciles de cometer. Hasta que aprendas como identificar rpidamente 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;
}
Despus que hemos creado la ventana y hemos chequeado para asegurarnos de que
tengamos un handle vlido, mostramos la ventana utilizando el ltimo parmetro
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 parmetro nCmdShow es opcional, simplemente puedes pasar el
valor SW_SHOWNORMAL en todo momento y listo. Sin embargo si usamos el parmetro
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...
Encontrars opciones para este parmetro en las propiedades de los iconos del
escritorio y apartir de estos valores ser determinado el valor que tome el parmetro
nCmdShow.

Paso 3: El Bucle de Mensajes


Este es el corazn del programa, la mayoria de las cosas que tu programa realiza
pasan a travs 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 aplicacin. 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 aplicacin. Utilizando GetMessage( ) ests 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 ests familiarizado con dicho
trmino, 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( ) enva el mensaje a la ventana que haba recibido
dicho mensaje. Esta podra ser nuestra ventana principal, otra ventana, o un control e
incluso una ventana que fu creada "detrs de la escena" por el sistema o algn otro
progama. Esto no es algo que debera precocuparte debido a que lo nico que nos
interesa saber es que obtenemos el mensaje y lo envamos, el sistema se encarga de
hacer el resto asegurndose de trabajar con la ventana correcta.

Paso 4: El Windows Procedure


Si el bucle de mensajes es el corazn del programa entonces el Windows Procedure es
el cerebro. Aqui es donde todos los mensajes son envados para que sean procesados
por nuestra ventana.
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 parmetro 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 usarn el mismo Windows Procedure (WndProc( ) ), la diferencia es
que el parmetro 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 parmetro) sabremos


que ventana es la que hay que destruir. Las dems ventanas no se vern afectadas,
solo la que recibi el mensaje.
WM_CLOSE es envado cuando el usuario presiona ALT+F4 o cuando presiona el
botn
. 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 enva el mensaje WM_DESTROY a la
ventana que va a ser destruda, 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 enva 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 cdigo del Bucle
de Mensajes, cuando esto sucede, dejamos de procesar mensajes y retornamos el
cdigo 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
diseado para ser llamado por otro programa y quieres que retorne un valor especifico.

Paso 5: No hay paso 5.


Bien, eso eso fu todo!(por ahora). Si las cosas an no se han entendido del todo, solo
sigue adelante y espera que las cosas se tornen mas claras, como cuando veamos
algunos programas.

Manejo de Mensajes
Ejemplo: window click
Bien, tenemos una ventana pero esta no hace nada excepto lo
que DefWindowProc( ) le permite que haga: cambiar el tamao, ser maximizada,
minimizada etc... Nada
realmente excitante.
En la siguiente seccin voy a
mostrar como modificar lo que
ya tenemos para que haga algo
nuevo. As, yo solo podra
decirte "Procesa este mensaje
y haz esto en l..." y tu sabrs
lo que digo y sers capaz de
hacerlo sin mirar un ejemplo entero. Esto es lo que espero, de todas maneras presta
atencin.

Ok, para comenzar tomemos el ejemplo de la ltima ventana en la que trabajamos y


asegurmonos que compila y se ejecuta como lo esperamos. Puedes seguir trabajando
en este archivo o puedes copiar el cdigo a un nuevo proyecto.
Vamos a agregarle a la ventana la capacidad de mostarle al usuario cul es el nombre
del programa. ESto se har cada vez que el usuario realice un click sobre la ventana.
Nada excitante, es bsicamente 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
botn 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 asegrate 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 cdigo 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 cdigo 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 sers capaz de agregar el cdigo dentro de
CUALQUIER programa, no solamente el que estamos presentando. De todas
maneras, si no ests seguro de como hacerlo puedes mirar el cdigo fuente
correspondiente a esta seccin includo en el archivo zip.
GetModuleFileName(hInstance, szFileName, MAX_PATH);
MessageBox(hwnd, szFileName, "This program is:", MB_OK |
MB_ICONINFORMATION);
Este cdigo 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 cdigo dentro de nuestro programa:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN:
// Comienzo del nuevo cdigo
{
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 cdigo
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 bsico de C pero
pienso que es bueno recordarlo para aquellos que estn haciendo las cosas por el
camino difcil.

Una vez que has agregado el cdigo, complalo. Si funciona, has click dentro de la
ventana y vers desplegado un cuadro con el nombre del archivo .exe .
Habrs notado que hemos agregado dos variables, hInstance y szFileName. Observa
la funcin GetModuleFileName y vers que el primer parmetro es
un HINSTANCE refirindose al mdulo ejecutable (nuestro programa,el archivo .exe).
Donde obtenemos eso? GetModuleHandle( ) es la respuesta. Las referencias
de GetModuleHandle( ) indican que si pasamos NULL como parmetro, 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 declaracin:
HINSTANCE hInstance = GetModuleHandle(NULL);
Ahora, con el segundo parmetro, 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 includa va <windows.h> definida como la mxima longitud de
un buffer necesario para almacenar el nombre de un archivo bajo Win32.
Pasamos MAX_PATH a la funcinGetModuleFileName( ) para que sta conozca el
tamao del buffer.
Luego de que GetModuleFileName( ) es llamado, el bffer szFileName ser llenado
con un string terminado en cero conteniendo el nombre de nuestro archivo exe.
Pasamos este valor aMessageBox() porque es una forma fcil de mostrarlo en pantalla.
Por lo tanto, si has agregado esto en el cdigo, complalo. Si funciona haz click en la
ventana y debers ver desplegado un mensaje con el nombre del archivo.
Si esto no funciona, aqu esta el cdigo completo. Compralo 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
wc.style
wc.lpfnWndProc
wc.cbClsExtra
wc.cbWndExtra
wc.hInstance
wc.hIcon
wc.hCursor
wc.hbrBackground
wc.lpszMenuName
wc.lpszClassName
wc.hIconSm

=
=
=
=
=
=
=
=
=
=
=
=

sizeof(WNDCLASSEX);
0;
WndProc;
0;
0;
hInstance;
LoadIcon(NULL, IDI_APPLICATION);
LoadCursor(NULL, IDC_ARROW);
(HBRUSH)(COLOR_WINDOW+1);
NULL;
g_szClassName;
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;

Comprender el Bucle de Mensajes


Entender el Bucle de Mensajes y la estructura de envo de mensajes es escencial para
escribir hasta el programa mas trivial. Ahora que hemos visto un poco sobre manejo
de mensajes deberamos mirar en profundidad todo este proceso porque las cosas se
tornarn mas confusas si no entendemos por qu las cosas suceden de la forma en
que lo hacen.

Que es un Mensaje?
Un mensaje es un valor entero. Si observas en tus archivos de cabecera (lo cual es una
buena prctica cuando investigamos el funcionamiento de las API`s) puedes encontrar
cosas como esta:
#define WM_INITDIALOG
#define WM_COMMAND

0x0110
0x0111

#define WM_LBUTTONDOWN
0x0201
...y asi siguiendo. Los mensajes son usados para comunicar la mayora de las cosas en
windows, al menos en los niveles bsicos. Si quieres que una ventana o control (el cual
es una ventana especializada) haga algo, debes enviarle un mensaje. Si otra ventana
quiere que vos hagas algo, entoces te enva un mensaje. Si ocurre un evento, como
cuando el usuario mueve el mouse, presiona el teclado, etc... entonces el sistema la
enva un mensaje a la ventana afectada. Dicha ventana recibe el mensaje y acta
adecuadamente.
Cada mensaje en windows puede tener hasta dos parmetros, wparam y lparam.
Originalmente wparam tena 16 bits y lparam tena 32 bits, pero en Win32 ambos son
de 32 bits. No todos los mensajes usan estos parmetros y cada mensaje los usa de
manera diferente. Por ejemplo, el mensaje WM_CLOSE no usa ninguno de los dos y por
lo tanto deberias ignorarlos. El mensajeWM_COMMAND usa ambos, wparam contiene dos
valores HIWORD(wparam) es la notificacin del mensaje (si se aplica)
y LOWORD(wparam) es el ID del control o men que envi el mensaje. lparam es

elHWND (Windows Handle) del control que envi el mensaje o nulo (NULL) si el mensaje
no proviene de un control.
HIWORD( ) y LOWORD( ) son macros definidas por windows que simplemente retornan
la palabra superior (High Word) y la palabra inferior (Low Word), respectivamente, de
un valor de 32 bits. En Win32 una palabra es un valor de 16 bits, haciendo
un DWord (palabra doble) un valor de 32 bits.
Para enviar un mensaje puedes
usar PostMessage( ) o SendMessage( ). PostMessage( ) pone el mensaje en la Cola
de Mensajes y retorna inmediatamente. Esto significa que una vez que la llamada
a PostMessage( ) se completa el mensaje puede o no puede haber sido procesado
an. SendMessage( ) enva el mensaje directamente a windows y no retorna hasta
que windows haya finalizado de procesarlo. Si quisieramos cerrar una ventana,
podramos enviarle a un mensaje WM_CLOSE de la siguiente
manera: PostMessage(hWnd,WM_CLOSE,0,0). Lo cual podra tener el mismo efecto que
hacer click en el boton cerrar de la ventana. Observa que wparam y lparam son
ambos 0. Esto es debido a que, como mencionamos, no se usan con el
mensaje WM_CLOSE.

Dilogos
Una vez que comiences a usar cajas de dilogo necesitars enviar mensajes a los
controles para poder comunicarte con ellos. Puedes hacer esto primero atravs
de GetDlgItem( ) (utilizando el ID como parmetro) para obtener el handle al control
y luego usar SendMessage( ). O puedes usar SendDlgItemMsg( ), el cual combina
ambos pasos. Como parmetros utilizamos el handle de la ventana y un ID de un hijo
y SendDlgItemMsg( ) retorna el handle del hijo y le enva el mensaje. Ambas formas
de enviar mensajes funcionan bien en todas las ventanas, no solamente en cajas de
dilogo.

Que es la Cola de Mensajes?


Digamos que te encuentras ocupado manejando el mensaje WM_PAINT y
repentinamente el usuario ingresa un gran cantidad de datos por el teclado. Que
debera suceder?. Deberas interrumpir el procesamiento de WM_PAINT (dejar de
dibujar la pantalla) para atender el teclado, o deberas simplemente descartar lo que el
usuario ingres por el teclado?. Error!, obviamente ninguna de stas opciones son
rasonables. Para esto tenemos la Cola de Mensajes. Cada vez que se recibe un
mensaje, dicho mensaje es encolado en la cola de mensajes y cada vez que se quiere
procesar un mensaje dicho mensaje es removido de la cola (valga la redundancia).
Esto asegura que no se pierdan mensajes, si ests procesando uno, entonces los otros
sern encolados hasta que los recuperes de la cola.

Que es el Bucle de Mensajes?

while(GetMessage(&Msg, NULL, 0, 0) > 0)


{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
1. El Bucle de Mensajes llama a GetMessage( ), el cual busca en tu cola de
mensajes. Si la cola de mensajes est vaca, tu programa se detiene y espera
hasta que haya un mensaje (se bloquea).
2. Cuando ocurre un evento, causando que un mensaje sea puesto en la cola (por
ejemplo el sistema registra un click del mouse) GetMessage( ) retorna un valor
positivo indicando que hay un mensaje para ser procesado y que ha llenado los
campos de la estructura MSG que le pasamos.
3. Tomamos el mensaje (en la variable Msg) y se lo pasamos
a TransalateMessage( ), ste hace un proceso adicional traduciendo mensajes
con teclas virtuales, en mensajes con caracteres. Este paso puede ser opcional,
pero ciertas cosas no funcionarn si no lo utilizamos.
4. Una vez que finalizamos de traducir el mensaje, se lo
pasamos DispatchMessage( ). Lo que DispatchMessage( ) hace es tomar el
mensaje, chequear a que ventana est destinado y buscar el Window Procedure
de dicha ventana. Luego llama a dicho procedimiento enviando como parmetro
el handle de la ventana, el mensaje, wparam y lparam.
5. En el Windows Procedure chequeamos el mensaje y sus parmetros y luego
hacemos lo que desamos con ellos. Si no estamos manejando dicho mensaje,
por lo menos siempre llamamos aDefWindowProc( ) el cual realizar las
acciones "por default" (lo cual significa que a veces no hace nada).
6. Una vez que hallas terminado de procesar el mensaje el Window Procedure
retorna, DispatchMessage() retorna y regresamos al comienzo del bucle.
Este es un concepto muy importante para los programas de windows. El Windows
Procedure no es mgicamente llamado por el sistema, de hecho lo llamamos
indirectamente atravs deDispatchMessage( ). Si deseas, puedes
usar GetWindowLong( ) en el handle de la ventana a la cual est destinado el mensaje
para buscar el Window Procedure de la misma y llamarlo directamente!
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}
He intentado esto con el cdigo del ejemplo anterior y funciona. Sin embargo hay
varios aspectos como la traduccin UNICODE/ANSI, entre otros, que este mtodo no
tendr en cuenta y probablemente haga fallar hasta la aplicacin mas trivial. Por lo
tanto hazlo como prueba pero no lo hagas en el cdigo real.

Observa que hemos usado GetWindowLong( ) para recuperar el Window Procedure


asociado con la ventana, pero por que no lo llamamos directamente?. Bien, nuestro
bucle de mensajes es responsable de TODAS las ventanas en nuestro programa, esto
incluye cosas como botones, listas, etc.. que tienen su propio Window Procedure
asociado, por lo tanto necesitamos asegurarnos que llamamos al procedimiento
correcto para una cierta ventana. Debido a que ms de una ventana puede usar el
mismo Window Procedure, usamos el primer parmetro (el handle de la ventana) para
decirle al Window Procedure a que ventana est destinado el mensaje.
Como puedes ver tu aplicacin pasa la mayor parte del tiempo dando vueltas y vueltas
en el bucle de mensajes donde alegremente envas mensajes a las felices ventanas
que los procesarn. Pero que haces cuando quieres que tu programa salga? Debido a
que estamos usando un bucle while( ) si GetMessage( ) retornara FALSE (i.e 0), el
bucle podra finalizar y de esta manera podramos alcanzar el final de
nuestro WinMain( ), es decir salir del programa. Esto es exactamente lo
que PostQuitMessage() realiza; pone un WM_QUIT en la cola y GetMessage( ) en vez
de retornar un valor positivo, llena la estrucura MSG y retorna 0. Hasta este punto el
campo wparam de la variable Msg contiene el valor que le pasamos
en PostQuitMessage( ) y podemos ignorarlo o retornarlo al WinMain( ) donde se
usar como valor de salida cuando el programa finalice.
IMPORTANTE: GetMessage( ) retornar -1 cuando encuentre un error. Asegrate de
recordar esto o te sorprender en algn momento... An cuando GetMessage( ) est
definido para retornar unBOOL (Booleano), ste puede retornar valores distinos
de TRUE o FALSE, debido a que BOOL est definido como UINT (unsigned int). Los
siguientes son ejemplos de cdigo que puede parecer que funciona pero no procesarn
ciertas condiciones correctamente.
while(GetMessage(&Msg, NULL, 0, 0))
while(GetMessage(&Msg, NULL, 0, 0) != 0)
while(GetMessage(&Msg, NULL, 0, 0) == TRUE)
Los segmentos de cdigo anterior estn mal! Quizs has notado que he usado el
primero de ellos en lo que va del tutorial, asi como lo mencion, este funciona bien
siempre y cuandoGetMessage( ) no falle y cuando tu cdigo es correcto, no lo hace.
Sin embargo no he tenido en cuenta que si ests leyendo este tutorial tu cdigo
probablemente no sea correcto en todo momento y GetMessage( ) puede fallar en
algn punto. Ya lo he corregido pero perdname si he olvidado algunos puntos.
while(GetMessage(&Msg, NULL, 0, 0) > 0)
Deberan usarse este o cdigos que tengam siempre el mismo efecto. Espero que
ahora tengas un mejor entendimiento del bucle de mensajes, si no es as, no te
asustes, las cosas tendrn mas sentido despus que hayan sido usadas un par de
veces.

Uso de Recursos

Puedes mirar los apndices al final del tutorial para mas informacin sobre el uso de
recursos con VC++ y BC++.
Antes de ver el tema en profundidad voy a cubrir el tema de recursos as no tendr
que reescribir el mismo en cada seccin. Por ahora no necesitas compilar el codigo,
solo es un ejemplo.
Los recursos son porciones pre-definidas de datos que se almacenan en formato
binario dentro de los archivos ejecutables y se organizan dentro de un archivo con
extensin ".rc" (resource script). Los compiladores comerciales cuentan con un editor
visual de recursos que permite crear recursos sin editar manualmente este archivo. A
veces la nica manera de poder crear un recurso es editar este archivo manualmente
sobre todo si tu compilador no tiene un editor visual o no soporta la caractersitica
exacta que necesitas.
Desafortunadamente, no todos los compiladores manejan los recursos de la misma
manera. Har lo mejor que pueda para explicar las caractersticas mas comunes que
son necesarias para trabajar con recursos en forma general.
El editor de recursos includo con MSVC++ hace muy difcil la tarea de editar los
recursos manualmente, debido a que fuerza un formato propio en ellos y si intentas
guardar uno que has creado manualmente estropea completamente el archivo. En
general no deberas preocuparte por crear archivos de recursos desde cero, pero
conocer como modificarlos manualmente puede ser muy til. Otra incomodidad es que
MSVC++ nombrar el archivo de recuros por default con el nombre "resource.h" an si
deseas llamarlo con otro nombre. Utilizaremos este con el propsito de simplicidad
para este documento, pero te mostrar como cambiarlo en el apndice sobre
compiladores.
Primero miremos un archivo de recursos bastante simple, con solo un cono:

#include "resource.h"
IDI_MYICON ICON "my_icon.ico"
Este es todo el archivo. IDI_MYICON es el identificador del recurso, ICON es el tipo de
recurso y "my_icon.ico" es el nombre del archivo externo que contiene al cono. Esto
debera funcionar en cualquier compilador.
Que hay acerca de #include "resource .h"?. Bien tu programa necesita una forma
de indentificar el cono y la mejor forma de hacerlo es asignndole a este un nico ID
(IDI_MYICON). Podemos hacer esto creando el archivo de cabecera "resource.h" e
incluir ste tanto en el archivo de recursos (.rc) como en el programa (.c)

#define IDI_MYICON

101

Como puedes ver, hemos asignado a IDI_MYICON el valor 101. De ahora en mas
podramos olvidarnos del identificador y simplemente usar 101 cada vez que
necesitamos referenciar el cono. PeroIDI_MYICON es un poco mas claro, mas
representativo y mas facil de recordar, sobre todo si estamos trabajando con una gran
cantidad de recursos.
Ahora supongamos que agregamos un recurso MENU:

#include "resource.h"
IDI_MYICON ICON "my_icon.ico"
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
END
END
Nuevamente IDR_MYMENU es el nombre del recurso y men es el tipo. Ahora observa
el BEGIN y el END. Algunos editores o compiladores utilizan la llave que abre { en lugar
de BEGIN y la llave que cierra } en lugar de END. Si tu compilador soporta ambos,
puedes elejir el que mas te guste si solo soporta uno u otro debrers hacer los
reemplazos necesarios en el codigo fuente para que esto funcione.
Tambin he agregado un nuevo identificador, ID_FILE_EXIT, por lo tanto necesitamos
agregarlo al archivo de cabecera de los recursos (resource.h) para poder usarlo dentro
de nuestro porgrama.

#define IDI_MYICON
#define ID_FILE_EXIT

101
4001

Generar y mantener un registro de todos los identificadores de recursos puede ser una
tarea costosa cuando trabajamos con grandes proyectos, esta es la razn por la que
muchas personas utilizan un editor visual de recursos, el cual se encarga de hacer todo
este trabajo por nosotros. Aunque pueden generar problemas de vez en cuando y
podras finalizar con multiples recursos con el mismo ID o problemas similares, es
bueno ser capaz de ir y poder corregir esos errores uno mismo.
Ahora veamos un ejemplo de como usar un recurso dentro de un programa.
HICON hMyIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYICON));
El primer parmetro de LoadIcon( ) y muchos otros recursos que utilizan funciones es
el handle a la instancia actual (la que obtenemos en WinMain( ) y que tambin puede
ser recuperada utilizando la funcin GetModuleHandle( ), como lo demostramos en
secciones anteriores). El segundo es el identificador del recurso.

Probablemente te ests preguntando que es MAKEINTRESOURCE( ) y posiblemente por


qu LoadIcon() toma un parmetro de tipo LPCTSTR en vez de, por ejemplo UINT,
cuando le estamos pasando un ID. Todo lo que MAKEINTRESOURCE hace es convertir un
entero (el tipo de nuestro ID) a un LPCTSTR que es lo que LoadIcon( ) espera. Esto
nos da una segunda forma de identificar recursos: utilizando strings. Hoy en dia casi
nadie hace esto, por lo tanto no voy a entrar mucho en detalle. Bsicamente si no
utilizas #define para asignar un valor entero a tus recursos, entonces el nombre es
interpretado como un string y podemos referenciar los recursos dentro de nuestro
programa de la siguiente manera:
HICON hMyIcon = LoadIcon(hInstance, "MYICON");
Cuando se les pasa un valor entero LoadIcon( ) y otras APIs que cargan recursos
pueden identificar la diferencia entre un entero y un puntero a un entero chequeando
la palabra superior de dicho valor. Si es cero (como podra ser el caso de un entero con
un valor menor a 65535) entonces asume que se trata del ID de un recurso. Esto
efectivamente nos limita a usar IDs inferiores a 65535, que a menos que tengas una
gran cantidad de recursos, no deberi ser un problema. Si es distinto de cero, entonces
asume que el valor es un puntero y busca el recurso por el nombre. Nunca le confes
este trabajo a la API a menos que est explicitamente establecido en la
documentacin. Por ejemplo, esto no funciona para comandos del men
como ID_FILE_EXIT debido a que solo pueden ser enteros.

Menes e Iconos
Ejemplo: menu_one
Esta es slo una pequea seccin para mostrar como agregar menes bsicos en
nuestra ventana. Generalmente utilizaremos un recurso men pre-definido que estar
en un archivo .rc y que luego ser compilado y enlazado con el archivo .exe. Esto suele
depender de cada compilador, los compiladores comerciales cuentan con un editor de
recursos que puedes utilizar para crear tus menes, pero en este ejemplo voy a
mostrar el texto del archivo .rc para que puedas agregar menes manualmente.
Generalmente tengo un archivo .h que incluyo dentro de mis archivos .c y .rc .Este
archivo contiene los identificadores para controles, items de un men, etc...
En este ejemplo puedes comenzar con el cdigo de la ventana que vimos en el ejemplo
simple_window, e ir agregando el cdigo de esta seccin.
Primero el archivo .h, generalmente llamado "resource.h"

#define IDR_MYMENU 101


#define IDI_MYICON 201
#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002

No hay demasiado aqu, pero nuestro men ser muy simple. Los nombres y valores
puedes escogerlos arbitrariamente. Ahora escribamos nuesto archivo .rc

#include "resource.h"
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
END

END

POPUP "&Stuff"
BEGIN
MENUITEM "&Go", ID_STUFF_GO
MENUITEM "G&o somewhere else", 0, GRAYED
END

IDI_MYICON ICON "menu_one.ico"

Debers agregar el archivo .rc a tu project o makefile dependiendo de que herramienta


ests usando.
Tambin debes agregar la linea #include "resource.h" en tu archivo fuente (.c) para
que los identificadores de los comandos del men y los del recurso men estn
definidos.
La forma mas fcil de asignar un men y un cono a nuestra ventana es especificar
ambos cundo registramos la clase ventana, como esto:
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MYMENU);
wc.hIcon = LoadIcon(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDI_MYICON));
wc.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 16, 16, 0);
Cambia esto y observa que sucede. Tu ventana ahora debera tener un men File y un
men Stuff, con los respectivos items dentro. Esto es asumiendo que nuestro archivo
.rc fu apropiadamente compilado y enlazado en nuestro programa. (nuevamente mira
las notas sobre compiladores)
He usado LoadIcon( ) para cargar el cono grande debido a que es mas simple, sin
embargo solo cargar los conos en la resolucin por default de 32x32, por lo tanto
para cargar la imagen pequea necesitamos usar LoadImage( ) .Observa que los
archivos de conos y de recursos pueden contener mltiples imgenes y en este caso,
la nica imagen que hay, contiene los dos tamaos que estamos cargando.
Ejemplo: menu_two

Una alternativa cuando usamos el recurso men es crear uno en tiempo de ejecucin.
Esto lleva un poco mas de trabajo de programacin, pero agrega mas flexibilidad y a
veces es necesario.
Tambin podemos usar conos que no estn almacenados como recursos, podramos
elegir almacenar nuestros conos en un archivo separado y cargarlos en tiempo de
ejecucin. Esto tambin podra darnos la opcin de permitir al usuario seleccionar un
cono con los dialogos comunes (common dialogs) que veremos luego, o alguna otra
opcin que tenga el mismo efecto.
Comenzaremos nuevamente con el ejemplo simple_window sin agregar el archivo .h y
el archivo .rc. Ahora manejaremos el mensaje WM_CREATE y agregaremos un men a
nuestra ventana.
#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002
Por esta vez pon estos dos ID's en el comienzo de tu archivo .c, debajo de
los #include's. A continuacin agregaremos el siguiente cdigo dentro de nuestro
manejador del mensaje WM_CREATE.
case WM_CREATE:
{
HMENU hMenu, hSubMenu;
HICON hIcon, hIconSm;
hMenu = CreateMenu();
hSubMenu = CreatePopupMenu();
AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File");
hSubMenu = CreatePopupMenu();
AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu,
"&Stuff");
SetMenu(hwnd, hMenu);
hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32,
LR_LOADFROMFILE);
if(hIcon)
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
else
MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK
| MB_ICONERROR);
hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16,
LR_LOADFROMFILE);
if(hIconSm)
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
else

MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK


| MB_ICONERROR);
}
break;
Este crea un men igual al que hemos definido en nuestro archivo de recursos y se lo
asigna a nuestra ventana. Un men que es asignado a una ventana es
automticamente removido cuando el programa termina, por lo tanto no necesitamos
preocuparnos por librarnos de l cuando el programa finalice.
El cdigo para los conos es muy simple, llamamos a LoadImage( ) dos veces: primero
para cargar el cono de 16x16 y luego el cono de 32x32. No podemos usar LoadIcon(
) en todo momento debido a que ste slo carga recursos, no archivos. En el
parmetro del handle a la instancia, especificamos el valor NULL debido a que no
estamos cargando un recurso de nuestro mdulo y en vez de pasar el ID de un
recurso, pasamos el nombre del archivo donde se encuentra el cono. Por ltimo,
pasamos el flag LR_LOADFROMFILE para indicar que la funcin trate el string que le
pasamos como un nombre de archivo y no como el nombre de un recurso.
Si esto tiene xito, asignamos el handle al icono utilizando WM_SETICON y si falla
mostramos un mensaje dicindonos que algo ha ocurrido.
NOTA: La llamada a LoadImage( ) fallar si el archivo con el cono no se encuentra
en el directorio de trabajo del programa. Si estas usando VC++ y ejecutas el programa
desde el IDE, el directorio de trabajo ser aquel donde se encuentre almacenado el
proyecto. Sin embargo si ejecutas desde el Debug, desde el explorer o la linea de
comando, entonces necesitars copiar el archivo con los iconos dentro de dicho
directorio para que el programa pueda encontrarlo. Si todo esto falla, especifica la ruta
completa.
Ok, ahora que tenemos un men necesitamos que haga algo. Esto es muy simple, todo
lo que necesitmaos hacer es manejar el mensaje WM_COMMAND. Adems necesitaremos
chequear que comando estamos recibiendo para poder actuar acordemente. Ahora
nuestro WinProc( ) debera lucir como este:
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM
lParam)
{
switch(Message)
{
case WM_CREATE:
{
HMENU hMenu, hSubMenu;
hMenu = CreateMenu();
hSubMenu = CreatePopupMenu();
AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu,
"&File");

hSubMenu = CreatePopupMenu();
AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu,
"&Stuff");
SetMenu(hwnd, hMenu);
hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32,
LR_LOADFROMFILE);
if(hIcon)
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
else
MessageBox(hwnd, "Could not load large icon!", "Error",
MB_OK | MB_ICONERROR);
hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16,
LR_LOADFROMFILE);
if(hIconSm)
SendMessage(hwnd, WM_SETICON, ICON_SMALL,
(LPARAM)hIconSm);
else
MessageBox(hwnd, "Could not load small icon!", "Error",
MB_OK | MB_ICONERROR);
}
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_FILE_EXIT:
break;
case ID_STUFF_GO:
break;
}
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
Como podemos ver, hemos procesado el mensaje WM_COMMAND y ste tiene adentro
otro switch( ). Este switch( ) distingue usando el valor de la palabra inferior de
wparam: si es el caso de un mensaje WM_COMMAND, entonces contiene el ID del control
o men que envi el mensaje.

Obviamente queremos que el item Exit cierre el programa. Por lo tanto en el


procesador del mensaje WM_COMMAND, ID_FILE_EXIT usaremos el siguiente cdigo:
PostMessage(hwnd, WM_CLOSE, 0, 0);
El procesador del mensaje WM_COMMAND debera lucir como este:
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_FILE_EXIT:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
case ID_STUFF_GO:
break;
}
break;
Te dejo como tarea realizar el otro comando ID_STUFF_GO para que haga algo.

El cono del Programa


Habrs notado que ahora el archivo menu_one.exe muestra como cono el cono que
agregamos como recurso, mientras que menu_two.exe no lo hace debido a que
estamos cargando un archivo externo. El Windows Explorer simplemente muestra el
primer icono (ordenados numericamente por ID) y debido a que slo tenemos un
cono, este es el que ser mostrado. Si quieres asegurarte de que un cierto cono sea
mostrado por el Windows Explorer simplemente agrega este como recurso y asgnale
un numero bajo de ID... como 1. De aqu en ms, en tu programa, ya no necesitas
referirte al archivo que contiene al cono y puedes asignarle a cada ventana un cono
diferente si lo deseas.

Dilogos, los mejores amigos de los programadores de GUIs


Ejemplo: dlg_one
Difcilmente exista un programa en Windows que no use cajas de dilogos.
Simplemente haz click en File/Open de cualquier editor de textos o cualquier otro tipo
de editor y sorpresa! ests enfrente de una caja de dilogo, una que probablemente
te permita escoger el archivo que deseas abrir.

Los dilogos no estn


limitados a los dilogos
estandar para abrir un
archivo, por el contrario,
pueden lucir como se te
ocurra. El punto atractivo de
los dilogos es que proveen
una forma rpida de ordenar
y crear Interfaces Graficas al
Usuario (GUI) y tambin
realizar algunos procesos por
default, disminuyendo la cantidad de cdigo que debemos escribir.
Una cosa para recordar es que los dilogos son solo ventanas. La diferencia entre
un dialogo y una ventana "normal" es que, con los dilogos, el sistema hace algunos
procesos adicionales por default, como crear e inicializar controles y manipular el orden
del tab. Todas las APIs que son aplicables a las ventanas "normales" funcionan
correctamente en los dilogos y viceversa.
El primer paso es crear el recurso dialogo. La manera en que se hace depende de cada
compilador/IDE, al igual que con todos los recursos. Aqui, voy a mostrarte el texto
plano del dialogo en el archivo .rc y debes incorporarlo tu mismo en tu proyecto.
IDD_ABOUT DIALOG DISCARDABLE 0, 0, 239, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "My About Box"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON
"&OK",IDOK,174,18,50,14
PUSHBUTTON
"&Cancel",IDCANCEL,174,35,50,14
GROUPBOX
"About this program...",IDC_STATIC,7,7,225,52
CTEXT
"An example program showing how to use Dialog
Boxes\r\n\r\nby theForger",
IDC_STATIC,16,18,144,33
END
En la primer lnea, IDD_ABOUTDLG es el ID del recurso. DIALOG es el tipo de recurso y
los cuatro numeros son: coordenada X de la esquina superior Izquierda, coordenada Y
de la esquina superior Izquierda, Ancho y Alto del dialogo. Estas NO SON PIXELES,
estn expresadas en unidades de dilogos que estn basadas en el tamao de la
fuente usada por el sistema (y elegida por el usuario). Si tienes seleccionada una
fuente grande el dialgo ser grande y si utilizas una fuente pequea el dialogo ser
mucho menor. Esto es importante porque asegura que todos los controles tengan el
tamao apropiado para poder mostrar adecuadamente el texto con la fuente
seleccionada. Puedes convertir unidades de dilogo a pixeles en tiempo de ejecucin
usando MapDialogRect( ).DISCARDABLE le dice al sistema que puede realizar un
"swap" para copiar el dialogo de la memoria al disco cuando no est siendo utilizado
para conservar recursos del sistema.
La segunda linea comienza con STYLE y sigue con los estilos de ventana que sern
usados para crear el dilogo. Esto debera estar explicado en CreateWindow() en tus

archivos de ayuda. Para poder usar las constantes predefinidas necesitamos


agregar #include "windows.h" en nuestro archivo de recursos .rc o en el caso de
VC++ podemos usar "winres.h" o "afxres.h". Si utilizas un editor de recursos estos
archivos sern includos automticamente, si es necesario.
Con CAPTION indicamos el ttulo del dilogo.
La lnea FONT especifica el nombre y tamao de la fuente que utilizaremos para este
dilogo. Esta puede no ser la misma para todas las computadoras, depende de las
fuentes que cada persona tenga instaladas y de los tamaos que haya especificado.
Ahora tenemos la lista de controles para crear el dilogo
DEFPUSHBUTTON
"&OK",IDOK,174,18,50,14
Esta es la lnea para el botn OK. El "&" indica que la letra que sigue (en este caso la
"O") puede ser combinada con la tecla Alt para activcar el control mediante el teclado
(Alt+O, en este caso). Todo esto es parte del proceso automtico que he
mensionado. IDOK es el identificador del control. IDOK es un control pre-definido por lo
tanto no necesitamos definirlo. Los cuatro nmeros al final son las coordenadas X e Y
de la esquina superior izquierda y el ancho y alto de botn, todas en unidades de
dilogos.
Si utilizas un editor de recursos para crear dilogos, esta informacin debera ser
puramente acadmica, de todos modos suele ser necesario conocer como hacerlo
directamente en el archivo de recursos (.rc), especialmente si no tienes un editor
visual de recursos.
Hay dos controles que tienen un ID IDC_STATIC (el cual es -1), esto es usado para
indicar que nunca necesitamos acceder a ellos, por lo tanto no necesitamos un
identificador. Sin embargo esto no quita la posibilidad de darle un ID o que editor de
recursos lo haga automticamente.
El texto "\r\n" en la linea del CTEXT es el par CR-LF que representa el comienzo de
una nueva linea.
Bien, habiendo agregado esto a nuestro archivo de recursos (.rc) necesitamos escribir
un Dialog Procedure para procesar los mensajes de este dialogo. No te preocupes esto
no es nada nuevo, prcticamente es lo mismo que con nuestro Window Procedure
(aunque no exactamente).
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM
lParam)
{
switch(Message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:

switch(LOWORD(wParam))
{
case IDOK:
EndDialog(hwnd, IDOK);
break;
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
}
break;
default:
return FALSE;

}
return TRUE;

}
Hay algunas diferencias importantes entre un Dialog Procedure y un Windows
Procedure. Una es que NO llamamos a DefWindowProc( ) para mensajes que no
manejamos. Con los dilogos esto es realizado automticamente.
Otra diferencia es que, generalmente, retornamos FALSE para aquellos mensajes que
no procesamos y TRUE para aquellos que si procesamos, A MENOS que el mensaje
especifique que valor debemos retornar. Observa que esto es lo que hicimos
anteriormente, por default, no debemos hacer nada y retornar FALSE, mientras que los
mensajes que si procesamos entran en el switch() y retornan TRUE.
La siguiente diferencia es que no llamamos a DestroyWindow() para cerrar el dilogo,
si no que utilizamos EndDialog( ). El segundo parmetro es el valor retornado a
quien halla llamado aDialogBox().
Por ltimo, en vez de procesar WM_CREATE, procesamos WM_INITDIALOG para realizar
cualquier proceso que sea necesario antes que aparezca el dilogo y luego
retornamos TRUE para darle el foco al teclado en el control asignado por default.
(Puedes procesar el mensaje WM_CREATE, pero ste es enviado ANTES que los controles
hayan sido creados, por lo tanto no puedes acceder a ellos. Cuando
procesamos WM_INITDIALOG los controles ya han sido creados, lo que nos permite
acceder a ellos).
Suficiente, hagmoslo...
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_HELP_ABOUT:
{
int ret = DialogBox(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
if(ret == IDOK){
MessageBox(hwnd, "Dialog exited with IDOK.", "Notice",
MB_OK | MB_ICONINFORMATION);
}
else if(ret == IDCANCEL){

MessageBox(hwnd, "Dialog exited with IDCANCEL.",


"Notice",

MB_OK | MB_ICONINFORMATION);
}
else if(ret == -1){
MessageBox(hwnd, "Dialog failed!", "Error",
MB_OK | MB_ICONINFORMATION);
}

}
break;
// Other menu commands...

}
break;
Este es el cdigo que he usado para crear mi dialogo "About", seguramente te
preguntas por que est puesto dentro del manejador WM_COMMAND, si no tienes claro
este aspecto, puedes revisar la seccin de menus e iconos. ID_HELP_ABOUT es el
dentificador del item men Help\About.
Debido a que queremos que el men de nuestra ventana pricipal cree el dilogo,
obviamnete debemos poner este cdigo en el WinProc( ) de nuestra ventana
principal, no en el Dialog Procedure.
Ademas, he almacenado los valores de retorno de las llamadas a DialogBox( ),
solamente con el propsito de observar el efecto de presionar dentro del dilogo los
dos Botones, Esc, Enter, etc... y poder ilustrar como utilizar los valores de retorno de
un dilogo para chequear si tubo xito, hubo alguna falla o para ver la eleccin del
usuario o alguna otra informacin que elijas para envirsela de retorno a quien lo llame
desde el Dialog Procedure.
DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd,
AboutDlgProc);
IDD_ABOUT es el ID del recurso dilogo. hwnd el el handle a la ventana padre del
dilogo. AboutDlgProc( ) es, por su puesto, el Dialog Procedure usado para controlar
el dilogo.
Un astuto lector eventualmente podra haberse preguntado: si DialogBox() no retorna
hasta que el dilogo sea cerrado, entonces no podemos procesar mensajes mientras el
dialogo est activo, como funciona esto?. Bueno, el tema es que los dilogos tienen su
propio bucle de mensajes, por lo tanto mientras el dilogo esta siendo mostrado en
pantalla (esta activo), nuestro bucle de mensajes es pasado a un segundo plano y
empieza a ejecutarse el bucle que Windows utiliza por default. Este bucle es manejado
por Windows y tambin se encarga de cosas como mover el foco del teclado de control
en control cada vez que presionamos la tecla Tab.
Otro efecto de usar DialogBox( ) es que la ventana principal queda desactivada hasta
que el dilogo sea despachado. A veces esto es lo que deseamos y otras veces no,
como cuando queremos usar un dilogo con una barra de herramientas flotante. En
este caso queremos ser capaz de interactuar con ambos, la ventana y el dilogo. Pero
esto ser visto en la prxima seccin.

Dilogos de Modelamiento
Ejemplo: dlg_two
Ahora echmosle un vistazo a CreateDialog( ), la hermana de DialogBox( ). La
diferencia es que mientras DialogBox() implementa su propio bucle de mensajes y no
retorna hasta que el dilogo sea cerrado, CreateDialog() acta de manera mas
parecida a una ventana creada con CreateWindowEx(), la cual retorna inmediatamente
y depende de tu bucle de mensajes enviar los mensajes a tu ventana principal. Esto es
llamado Modelamiento mientras
queDialogBox() crea dilogos Modales.
Podemos crear el recurso dilogo de la misma
manera que lo hicimos en el ejemplo anterior,
tambin debemos asignar el estilo extendido
"Tool Window" (ventana de herramientas) para
darle a la barra de titulo el tpico ttulo pequeo
de las barras de herramientas. El recurso
dilogo que he creado es el siguiente:
IDD_TOOLBAR DIALOGEX 0, 0, 98, 52
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
EXSTYLE WS_EX_TOOLWINDOW
CAPTION "My Dialog Toolbar"
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON
"&Press This Button",IDC_PRESS,7,7,84,14
PUSHBUTTON
"&Or This One",IDC_OTHER,7,31,84,14
END
Habrs observado que el editor de recursos ha
reemplazado DIALOG con DIALOGEX indicando que queremos asignar un EXSTYLE en
nuestro dilogo.
A continuacin, queremos crear el dilogo cuando el programa se ejecuta y ademas
queremos que el dilogo sea visible, por lo tanto hacemos esto en WM_CREATE. Tambin
queremos declarar una varialbe global para almacenar el handle retornado
por CreateDialog( ), que usaremos luego. DialogBox( ) no retorna un handle
debido a que cuando ste retorna, el dilogo ha sido destruido.
HWND g_hToolbar = NULL;
case WM_CREATE:
g_hToolbar = CreateDialog(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_TOOLBAR),
hwnd, ToolDlgProc);
if(g_hToolbar != NULL)
{
ShowWindow(g_hToolbar, SW_SHOW);
}
else
{
MessageBox(hwnd, "CreateDialog returned NULL", "Warning!",

MB_OK | MB_ICONINFORMATION);
}
break;
Es una buena idea chequear siempre los valores de retorno, y si son vlidos (distintos
de NULL) mostramos la ventana usando ShowWindow(), con DialogBox( ) esto no es
necesario debido a que el sistema llama a ShowWindow() por nosotros.
Ahora necesitamos un Dialog Procedure para nuestra barra de herramientas.
BOOL CALLBACK ToolDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM
lParam)
{
switch(Message)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_PRESS:
MessageBox(hwnd, "Hi!", "This is a message",
MB_OK | MB_ICONEXCLAMATION);
break;
case IDC_OTHER:
MessageBox(hwnd, "Bye!", "This is also a message",
MB_OK | MB_ICONEXCLAMATION);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
La mayora de las reglas de procesamiento de mensajes se aplican a los dilogos
creados con CreateDialog( ) y al igual que con DialogBox( ) no llamamos
a DefWindowProc( ), retornamos FALSEen los mensajes que no procesamos y TRUE en
los que si lo hacemos.
Un cambio es que, en los dilogos de modelamiento, no llamamos a EndDialog( ),
simplemente usamos DestroyWindow( ) como lo hacamos con las ventanas regulares.
En este caso, destrumos el dilogo cuando la ventana principal es destruda. En
el WndProc() de la ventana principal...
case WM_DESTROY:
DestroyWindow(g_hToolbar);
PostQuitMessage(0);
break;
Por ltimo, pero no menos importante, queremos ser capaz de mostrar y ocultar
nuestra barra de herramientas cada vez que el usuario lo desee, por lo tanto
agregamos dos comandos al men y los procesamos as:
case WM_COMMAND:
switch(LOWORD(wParam))
{

case ID_DIALOG_SHOW:
ShowWindow(g_hToolbar, SW_SHOW);
break;
case ID_DIALOG_HIDE:
ShowWindow(g_hToolbar, SW_HIDE);
break;
//... other command handlers
}
break;
Hasta aqu, debes ser capaz de crear tu propio men usando el editor de recursos o
manualmente, si no es as (como pasa siempre) puedes observar el ejemplo dlg_two
que viene junto a este tutorial.
Cuando ejecutes el programa, debes ser capaz de acceder a la ventana de dilogo y a
la ventana pricipal al mismo tiempo.
Si ejecutaste el programa e intentaste correr el foco entre los dos botones usando la
tecla tab, probablemente habrs observado que esto no funciona, como asi tampoco
funciona activar los botones presionando Alt-O o Alt-P. Por que
no? DialogBox() implementa su propio bucle de mensajes y procesa estos eventos
por default, pero CreateDialog( ) no lo hace. Podemos hacerlo nosotros, llamando
a IsDialogMessage( ) en nuestro bucle de mensajes, el cual har los procesos por
default en lugar nuestro.
while(GetMessage(&Msg, NULL, 0, 0))
{
if(!IsDialogMessage(g_hToolbar, &Msg))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
Aqui, primero pasamos el mensaje a IsDialogMessage( ), si el mensaje est
destinado a nuestra barra de herramientas (indicado por el handle que le pasamos) el
sistema realizar los procesamientos por default y retornar TRUE. Si es el caso en que
los mensajes ya han sido procesados, entonces no llamamos
a TransalateMessage( ) o DispatchMessage( ) y si el mensaje es para otra ventana,
lo procesamos de la manera usual.
Es importante notar que IsDialogMessaje( ) tambin puede ser usado con ventanas
que no son dilogos, con la intencin de darles el comportamiento de un dilogo.
Recuerda que un dilogo es una ventana y la mayora (por no decir todas) de las API's
para dilogos funcionarn correctamente en cualquier ventana.
Un aspecto para destacar es que pasa si tienes mas de una barra de herramientas?
Bien, una posible solucin es tener una lista (o un arreglo) y ciclar en nuestro bucle de
mensajes pasando cada handle a IsDialogMessage( ) hasta que encontremos el
correcto y si no lo es, hacemos el procesamiento regular. Este es un problema de
programacin genrico y no uno que est relacionado con Win32, por lo tanto se deja
como ejercicio para el lector.

Controles estndar: Botones,


Edicin, Cajas de lista, Estticos
Ejemplo: ctl_one
Ya hemos usado botones en ejemplos anteriores, por lo tanto deberas estar mas o
menos familiarizado con ellos, sin embargo debido a que he usado botones en este
ejemplo los he agregado como parte del
ttulo solo con el propsito de
completitud.

Controles
Una cosa para recordar acerca de los
controles es que son solo ventanas. Al
igual que cualquier otra ventana tienen
un window procedure, una clase
ventana etc... que es registrada por el
sistema. Todo lo que puedes hacer con
las ventanas normales, puedes hacerlo
con los controles.

Mensajes
Como recordars de las primeras discusiones sobre el bucle de mensajes, Windows se
comunica usando mensajes, envamos mensajes para que un control realice algo y
cuando un evento ocurre sobre un control, ste nos enva un mensaje de notificacin.
Para controles estndar esta notificacin ser un mensaje WM_COMMAND, como sucede
con los botones y menes y para los Controles Comunes, de los cuales hablar
despus, esta notificacin ser el mensaje WM_NOTIFY.
Los mensajes varan ampliamente para cada control, y cada control tiene su propio
conjunto de mensajes. En algn momento el mismo mensaje ser usado para mas de
un tipo de control, pero en general solo funcionarn en el control para el cual estn
destinados. Esto es especialmente molesto con los mensajes listbox y combobox (LB*
y CB* ) los cuales si bien realizan tareas casi idnticas NO SON intercambiables.
Por otro lado, los mensajes genricos como WM_SETTEXT son soportados por casi todos
los controles. Despus de todo un control es una ventana.
Puedes enviar mensajes usando la API SendMessage( ) y usar GetDlgItem( ) para
obtener el handle al control, o puedes usar SenDlgItemMessage( ) el cual realiza
ambos pasos. En ambos mtodos el resultado es el mismo.

Controles Edit

Uno de los controles mas comunmente usados en el entorno Windows, el control EDIT,
es usado para permitir al usuario ingresar texto, modificar, copiar, etc... El Block de
Notas de Windows no es mas que una ventana con un gran control edit dentro de ella.
Aqui est el cdigo usado como interface para el control edit de este ejemplo:
SetDlgItemText(hwnd, IDC_TEXT, "This is a string");
Esto es para cambiar el texto contenido dentro del control (esto puede ser usado para
cualquier control que tenga un valor de texto asociado a l, STATICs, BUTTONs, etc...).
Recuperar el texto de un control tambin es fcil, si bien es apenas mas trabajo que
cambiarlo...
int len = GetWindowTextLength(GetDlgItem(hwnd, IDC_TEXT));
if(len > 0)
{
int i;
char* buf;
buf = (char*)GlobalAlloc(GPTR, len + 1);
GetDlgItemText(hwnd, IDC_TEXT, buf, len + 1);
//Rellenamos con texto...
GlobalFree((HANDLE)buf);
}
Primero que todo, necesitamos reservar memoria para almacenar el string, para esto
necesitamos conocer cuanta memoria reservar. No hay una
funcin GetDlgItemTextLenght( ), pero existeGetWindowTextLenght( ), por lo tanto
todo lo que necesitamos hacer es obtener el handle al control usando GetDlgItem( ).
Ahora que sabemos la longitud, podemos reservar la memoria necesaria para
almacenar el string. Aqu, he agregado un chequeo para ver si existe algn texto (i.e la
longitud del string es mayor que cero) para no estar trabajando sobre un string vaco.
Asumiendo que existe dicho texto llamamos a GlobalAlloc( ) para reservar la
memoria. Si eres usuario de DOS/UNIX GlobalAlloc( ) es equivalente a calloc( ).
Esto reserva algo de memoria, inicializa su contenido a cero (0) y retorna un puntero a
dicha memoria. Hay diferentes flags que puedes pasar en el primer parmetro para
hacer que se comporte de manera diferente para diferentes propsitos, pero esta es la
nica manera en que la voy a usar durante el resto del tutorial.
Observa que le he sumado 1 a la longitud del string en dos lugares, Por que?
Bien, GetWindowTextLenght( ) retorna el nmero de caracteres que contiene el texto
del control SIN INCLUIR el terminador nulo. Esto significa que si no sumamos 1 el
texto podra caber pero el terminador nulo sobrepasara los limites del bloque de
memoria, causando una violacin de acceso o sobreescribiendo otros datos. Debes ser
cuidadoso cuando trabajas con longitudes de strings en windows, debido a que algunas
APIs y mensajes esperan que las longitudes incluyan el caracter nulo y otras no lo
hacen. Siempre lee la documentacin cuidadosamente.

Finalmente podemos llamar a GetDlgItemText( ) para recuperar los contenidos del


control en el buffer de memoria que acabamos de crear. Esta llamada espera el tamao
del buffer incluyendo el terminador nulo. El valor, el cual vamos a ignorar aqu, es el
nmero de caracteres copiados SIN incluir el terminador nulo... divertido eh?
Hay un segundo conjunto de APIs llamadas LocalAlloc( ), LocalFree( ), etc... que
son parte de las APIs de Windows 16-bits. En Win32 las funciones para
memoria Local* y Free* son identicas.

Edits con Numeros


Cuando ingresamos texto est todo bien, pero que pasa si queremos que el usuario
ingrese un numero? Esta es una tarea muy comn y afortunadamente hay una API
para hacer esto mas simple. Esta API se encarga de toda la asignacin de memoria,
como tambien convertir el string a un valor entero.
BOOL bSuccess;
int nTimes = GetDlgItemInt(hwnd, IDC_NUMBER, &bSuccess, FALSE);
GetDlgItemInt( ) funciona de manera muy parecida a GetDlgItemText( ), excepto
que en vez de copiar al string a un buffer, lo convierte internamente a un entero y
luego retorna el valor. El tercer parmetro es opcional y toma un puntero a un BOOL.
Debido a que la funcin retorna 0 en caso de falla, no hay forma de distinguir si la
funcin ha fallado o el usuario ha ingresado como valor el numero 0, por lo tanto
usamos este parmetro. Pero si te resulta adecuado utilizar el valor 0 en caso de error,
entonces puedes ignorar este parmetro.
Otra caracterstica muy usada para controles de edicin es el estilo ES_NUMBER, el cual
permite que solo sean ingresados los caracteres del 0 al 9. Esto el muy til si solo
quieres que se ingresen enteros positivos, en otro caso no tiene mucha utilidad debido
a que no puedes ingresar caracteres como el menos (-), el punto decimal (.) o la coma
(,).

Listas
Otro control muy usado son las listas. Este es el ltimo control estandar que voy a
cubrir por ahora, debido a que francamente no hay muchos interesantes y si an no
ests aburrido, yo si lo estoy :)

Agregado de Items
La primer cosa que quieres hacer con una lista es agregar items en ella.
int index = SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0,
(LPARAM)"Hi there!");
Como puedes ver, esta es una tarea muy simple. Si la lista tiene el estilo LBS_SORT, el
nuevo item ser agregado en orden alfabtico, en otro caso simplemente ser
agregado al final de la lista.

Este mensaje retorna el ndice del nuevo item, el cual podemos usar para realizar
alguna tarea sobre el tem, como asociar algunos datos a l. Generalmente esto es
como un puntero a una estructura conteniendo mas informacin, o quizs un ID que
usars para identificar el tem.
SendDlgItemMessage(hwnd, IDC_LIST, LB_SETITEMDATA, (WPARAM)index,
(LPARAM)nTimes);

Notificaciones
El proposito de las listas es permitir al usuario seleccionar cosas de una lista. Sin
embargo, a veces queremos ser capaz de hacer algo en el acto, quizs mostrar
informacin diferente o actualizada, basada en los items que fueron seleccionados.
Para hacer esto necesitamos procesar los mensajes de notificacin que nos enva la
lista. En este caso estamos interesados enLBN_SELCHANGE, el cual nos dice que la
seleccin ha sido modificada por el usuario. LBN_SELCHANGE es enviado
via WM_COMMAND, pero a diferenecia del procesamiento de los mensajes WM_COMMAND que
hacemos con los botones y menes, que son generalmente en respuesta a un click,
una lista enva el mensaje WM_COMMAND por varias razones y necesitamos un segundo
chequeo para averiguar que nos est diciendo. El Cdigo de Notificacin es pasado
como el HIWORD de wParam, la otra mitad del parmetro nos da el ID del control.
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_LIST:
// It's our listbox, check the notification code
switch(HIWORD(wParam))
{
case LBN_SELCHANGE:
// Selection changed, do stuff here.
break;
}
break;
// ... other controls
}
break;

Obtener los datos de una lista


Ahora que sabemos que la seleccin ha cambiado, necesitamos obtener dicha seleccin
de la lista y realizar algo til con ella.
En este ejemplo he usado una lista multi-seleccin, por lo tanto obtener la lista de
items seleccionados es un poco mas complicado. Si fuera una lista de seleccin simple,
podramos simplemente envar LB_GETCURSEL para recuperar el ndice del item.
Primero, necesitamos obtener los indices de los items seleccionados para que podamos
reservar un bffer de memoria en donde guardar los ndices.
HWND hList = GetDlgItem(hwnd, IDC_LIST);
int count = SendMessage(hList, LB_GETSELCOUNT, 0, 0);

Luego, reservamos un bffer basado en el nmero de items, y enviamos el


mensaje LB_GETSELITEM para llenar el arreglo.
int *buf = GlobalAlloc(GPTR, sizeof(int) * count);
SendMessage(hList, LB_GETSELITEMS, (WPARAM)count, (LPARAM)buf);
// ... Do stuff with indexes
GlobalFree(buf);
En este ejemplo, buf[0] es el primer ndice, buf[1] el segundo, etc.. siendo el ltimo
indice buf[count-1].
Una de las cosas que podrias querer realizar con la lista de ndices, es recuperar los
datos asociados con cada item y luego hacer algn proceso con ste. Esto se hace
simplemente enviando otro mensaje.
int data = SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);
Si los datos tienen algn otro tipo de valor (cualquier cosa distinta de 32-bits)
simplemente debemos convertirlo al tipo apropiado. Por ejemplo si
almacenamos HBITMAPs en vez de ints...
HBITMAP hData = (HBITMAP)SendMessage(hList, LB_GETITEMDATA,
(WPARAM)index, 0);

Estticos
Al igual que los botones, los controles estticos son ampliamente triviales, pero una
vez mas por el propsito de completitud, voy a incluirlos aqu. Los controles estticos
son generalmente aquello que es, esttico, significando que no cambian o no hacen
nada en especial. Son muy usados para mostrar texto al usuario, sin embargo puedes
hacer de ellos algo un poco mas util asignndoles un nico ID (VC++ asigna un ID por
default, IDC_STATIC, que tiene un valor de -1 y significa "Sin ID") y luego asignarles el
texto en tiempo de ejecucin para presentar texto dinmico al usuario.
En el ejemplo, he usado uno para mostrar los datos de los items seleccionados en la
caja de lista, asumiendo que se puede seleccionar solo un item.
SetDlgItemInt(hwnd, IDC_SHOWCOUNT, data, FALSE);

Dilogos FAQ
Ejemplo: dlg_three
No confundan, esto es un Tutorial, no una Referencia, pero las preguntas de algunas
personas son tan frecuentes que he pensado que sera bueno incluirlas aqui.

Cambio de los colores


En general la nica razn por la cual querras
hacer esto debe ser para simular un link en
un dilogo o alguna tarea similar, porque en
otro caso estaras haciendo que tu programa
luzca horroroso a la vista si agregas una gran cantidad de colores a los dilogos. De
todas maneras esto no impide a las personas de hacerlo y hay algunas razones vlidas,
aqu van :)
Windows enva a nuestro dialog procedure una gran cantidad de mensajes relacionados
a los colores y procesando dichos mensajes podemos cambiar el color de ciertas cosas.
Por ejemplo para cambiar el color de el dilogo en si mismo podemos procesar el
mensaje WM_CTLCOLORDLG, para cambiar el color de un control esttico podemos
procesar el mensaje WM_CTLCOLORSTATIC, etc...
Primero debemos crear un pincel (Brush) para pintar el fondo y luego almacenarlo para
poder usarlo despus. El mensaje WM_CTLCOLORDLG y otros mensajes relacionados
sern llamados an durante el trancurso de la ejecucin de nuestro programa y si cada
vez que esto sucede creamos un pincel podriamos desperdiciar una gran cantidad de
RAM con pinceles muertos. De esta forma tenemos mas control y podemos borrarlo
cuando el dilogo es destrudo, es decir, cuando ya sabemos que no lo vamos a
necesitar mas.
HBRUSH g_hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
case WM_CTLCOLORDLG:
return (LONG)g_hbrBackground;
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC)wParam;
SetTextColor(hdcStatic, RGB(255, 255, 255));
SetBkMode(hdcStatic, TRANSPARENT);
return (LONG)g_hbrBackground;
}
break;
Observa la lnea que fija el color de fondo en modo transparente... si quitamos esta
linea el fondo ser llenado con el pincel que hemos especificado, pero cuando el control
dibuje el texto, ste ser escrito encima con el color de fondo por default! Poniendo el
modo de dibujo de texto en transparente corregimos este problema. La otra opcin
sera usar SetBkColor( ) para poner como color de fondo el mismo color del pincel,
pero prefiero la otra solucin.
Cambiar los colores en la mayoria de los controles estndar funciona de la misma
manera, solo hay que observar los mensajes WM_CTLCOLOR* en la referencia de Win32.
Observa que un control Edit enviar un mensaje WM_CTLCOLORSTATIC si es de solo
lectura y WM_CTLCOLOREDIT si no lo es.
Si tienes mas de un control esttico y quieres que tengan diferentes colores, entonces
cuando procesas los mensajes, necesitars chequear el ID del control que enva el
mensaje para identificar de que control proviene y asi cambiar los colores al control

adecuado. Tenemos el HWND del control en lParam y a partir de este podemos obtener
el ID del control usando GetDlgCtrlID( ). Observa que el editor de recursos les da a
los controles estticos un ID IDC_STATIC igual a -1, por lo tanto si necesitamos
distinguirlos debemos asignarles nuevos IDs.

Poner un cono en el Dilogo


Es una tarea simple, solo necesitamos enviar a nuestro dilogo el
mensaje WM_SETICON. Sin embargo, debido a que Windows utiliza dos conos,
necesitamos enviar el mensaje dos veces: una vez para el cono pequeo mostrado en
la esquina de la ventana y otra vez para el cono grande mostrado cuando presionamos
Alt+Tab. Podemos enviar el mismo handle ambas veces, a menos que tengamos iconos
de diferentes tamaos.
Para poner el icono de aplicacin por default, podemos usar el siguiente cdigo:
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(NULL,
MAKEINTRESOURCE(IDI_APPLICATION)));
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon(NULL,
MAKEINTRESOURCE(IDI_APPLICATION)));
Cuando reemplazes tu propio cono por uno por default, recuerda cambiar el
parmetro HINSTANCE de LoadIcon( ) por el de la instancia de tu aplicacin (si no lo
tienes almacenado en WinMain( ), puedes obtener ste llamando a GetModuleHandle(
)).

Por que no funciona mi Combo Box?


Uno de los problemas mas comunes que las personas tienen cuando agregan un
Combo Box a sus dialogos es que no pueden darse cuenta por qu la lista no puede ser
mostrada cuando corren sus programas y hacen click en la flecha pequea. Esto es
entendible, debido a que la solucin no es muy intuitiva.
Cuando creamos un combo box y especificamos su altura, estamos especificando la
altura entera, con la lista despelegada incluida, NO la altura del control cuando no est
desplegado, ya que sta es determinada por el sistema de acuerdo al tamao de la
fuente usada.
Por ejemplo, si le damos al control una altura de 100 pixeles, el sistema ajusta el
tamao del control con un valor por default (digamos 30 en este caso) y cuando
hacemos click en la flecha la lista desplegada podra ser de una mximo de 70 pixeles,
haciendo un total de 100 pixeles.
Si utilizas el editor de recursos de VC++ para ubicar el combo box dentro de tu
dialogo, observars que no puedes cambiar el tamao vertical. A menos que hagas
click en la flecha en el editor y ste cambiar el foco para indicar que ests cambiando
el tamao de la lista desplegada. Luego puedes especificar el ancho con el valor que
quieras.

Que pasa con todos los otros controles?


Bien, podra dar ejemplo de todos los otros controles, pero es lo que hacen MSDN y
Petzold :) Si no puedes darte cuenta de como usarlos, probablemente necesites re-leer
algunas partes de este tutorial, o conseguir un libro que explique las cosas mas
ampliamente.
Quiero darte un link a una pgina muy buena en MSDN, pero Microsoft parece estar
determinado a impedirme dar links a pginas individuales, debido a que cambian
rpidamente o no funcionan por un perodo. Por lo tanto tendrs que darte cuenta
como llegar vos mismo, busca las secciones como User Interface Services y Windows
Controls, a veces dentro de la seccin Plataform
MSDN - Windows Controls

Aplicacin Parte 1: Crear


Controles en Tiempo de
Ejecucin
Example: app_one
Pienso que dar un ejemplo sobre crear controles en ejecucin, si bien es muy usado,
podra ser en vano a menos que la aplicacin realmente haga algo til, por lo tanto en
esta seccin voy a comenzar con el desarrolo de un editor de texto y lo iremos
desarrollando hasta que alcanzemos un programa til que soporte abrir, editar y
guardar archivos de textos.
El primer paso, el cual es cubierto por esta
seccin, ser simplemente crear la ventana y el
control EDIT que servir como centro de
nuestro programa.
Comenzaremos con el esquelto del cdigo de la
aplicacin Simple Window, agregaremos
un #define para el ID de nuestro control y los
siguientes dos manejadores de mensajes en
nuestro window procedure:
#define IDC_MAIN_EDIT 101
case WM_CREATE:
{
HFONT hfDefault;
HWND hEdit;
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL |
ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,

0, 0, 100, 100, hwnd, (HMENU)IDC_MAIN_EDIT,


GetModuleHandle(NULL), NULL);
if(hEdit == NULL)
MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK
| MB_ICONERROR);
hfDefault = GetStockObject(DEFAULT_GUI_FONT);
SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault,
MAKELPARAM(FALSE, 0));
}
break;
case WM_SIZE:
{
HWND hEdit;
RECT rcClient;
GetClientRect(hwnd, &rcClient);
hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom,
SWP_NOZORDER);
}
break;

Crear los controles


Para crear los controles, al igual que lo hacemos para cualquier otra ventana,
utilizamos la API CreateWindowEx( ). Pasamos una clase pre-registrada, en este caso
la clase del control "EDIT", y obtenemos un control edit estndar. Cuando usamos
dilogos para crear nuestros controles, bsicamente estamos escribiendo una lista de
controles a crear, tal que, cuando llamamos a DialogBox( ) o CreateDialog( ) el
sistema lee la lista de controles en el recurso dilogo y por cada uno llama
a CreateWindowEx( ), con la posicin y estilo con que fueron definidos.
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE |
ES_AUTOVSCROLL | ES_AUTOHSCROLL,
0, 0, 100, 100, hwnd, (HMENU)IDC_MAIN_EDIT,
GetModuleHandle(NULL), NULL);
if(hEdit == NULL)
MessageBox(hwnd, "Could not create edit box.", "Error", MB_OK |
MB_ICONERROR);
Puedes ver que esta llamada a CreateWindowEx( ) especifica cierta cantidad de
estilos y no es extrao tener alguno mas, especialmente para los
controles comunes los cuales tienen una larga lista de opciones. Los
primeros cuatro estilos WS_ deberian ser obvios, estamos creando el
control como un hijo de nuestra ventana, queremos que sea visible y tenga
barras de desplazamiento vertical y horizontal. Los tres estilos que son
especficos de los controles EDIT (ES_MULTILINE | ES_AUTOVSCROLL |
ES_AUTOHSCROLL) especifican que el control edit debe poseer mltiples
lineas de texto y desplazarse automticamente cuando tipeamos mas alla
del lmite inferior de la ventana y del limite derecho de la misma.
Los estilos regulares (WS_*) puedes encontrarlos en: listed here. y los
estilos extendidos (WS_EX_*) son explicados en: CreateWindowEx() estas

referencias son de MSDN donde tambin puedes encontrar links a los


estilos especficos de cada control (ES_* en el caso del control EDIT).
Hemos especificado nuestro window handle como padre del control y le
asignamos un ID de IDC_MAIN_EDIT, el cual usaremos luego para referirnos
al control, de la misma manera que lo hariamos si el control hubiera sido
creado en un dilogo. Los parmetros de posicin y tamao no importan
mucho por el momento, debido a que cambiaremos el tamao del control
dinmicamente con el mensaje WM_SIZE para que ste siempre entre en
nuestra ventana.

Cambio del tamao de controles creados dinmicamente


Generalmente, si el tamao de nuestra ventana puede ser cambiado,
tendremos algn cdigo para reposicionar o ajustar el tamao de los
controles que hemos creado dentro de ella, para que siempre sean
mostrados apropiadamente.
GetClientRect(hwnd, &rcClient);
hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
SetWindowPos(hEdit, NULL, 0, 0, rcClient.right, rcClient.bottom,
SWP_NOZORDER);
Debido a que por ahora solo tenemos un control, la tarea es relativamente
simple. Usamos GetClientRect( ) para obtener las dimensiones del Area
Cliente de la ventana, la gran rea vaca (por ahora) que no inlcluye los
bordes, men o ttulo. Esto llenar nuestra estructura RECT con valores.
Los valores left y top siempre sern 0, por lo tanto puedes ignorarlos.
Los valores right and bottom indican el ancho y el alto del area cliente.
A Continuacin, simplemente obtenemos el handle a nuestro control EDIT
usando GetDlgItem( ), el cual funciona bien en las ventanas regulares
como en los dilogos y la llamadaSetWindowPos( ) para mover y ajustar el
tamao para llenar todo el rea cliente. Por su puesto, puedes cambiar
los valores pasados en SetWindowPos( ) para hacer algo como slo llenar
la mitad del alto de la ventana, dejando libre la parte inferior para
ubicar otros controles.

Crear otros controles en tiempo de ejecucin


No voy a dar ejemplos de como crear dinmicamente los otros controles,
como listas, botones, etc... debido a que es bsicamente lo mismo. Si
visitas los links anteriores en MSDN o buscas en tu referencia de Win32
API, encotrars toda la informacin necesaria para crear cualquiera de
los otros controles estndar.
Veremos mas sobre esto con los controles comunes, en la siguientes
secciones, donde obtendrs mas prctica.

Aplicacin Parte 2: Uso de


archivos y Dilogos Comunes

Ejemplo: app_two

Dilogos Comunes
para archivos
El primer paso para abrir o guardar
archivos es obtener el nombre del
archivo a usar... aunque, simplemente
podramos poner el nombre del archivo
en el cdigo de nuestro programa, pero
honestamente esto no es muy til en la
mayora de los programas.
Debido a que esto es una tarea bastante
comn, existen dilogos predefinidos
por el sistema que pueden ser usados para permitir al usuario seleccionar el nombre
de un archivo. Los dilogos mas comunes, para abrir y para guardar archivos, son
accedidos a travs de GetOpenFileName( )and GetSaveFileName( ) respectivamente,
ambos de los cuales utilizan una estructura OPENFILENAME.
OPENFILENAME ofn;
char szFileName[MAX_PATH] = "";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn); // Mira la nota a continuacin
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Text Files (*.txt)\0*.txt\0All Files
(*.*)\0*.*\0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrDefExt = "txt";
if(GetOpenFileName(&ofn))
{
// Do something usefull with the filename stored in szFileName
}
Observa que en la estructura llamamos a ZeroMemory( ) para inicializarla a 0. Esto es
generalmente una prctica prudente, dado que muchas APIs suelen asignar los campos
que no utilizamos conNULL y de esta forma no necesitamos asignar cada miebro que
no utilizamos.
Puedes encontrar fcilmente el significado de cada uno de los campos de la estructura
en la documentacin. El campo lpstrFilter apunta a un string doblemente terminado
en NULL, puedes ver en el ejemplo que hay varios "\0" a travs de ste, incluyendo
uno al final... el compilador agregar el segundo al final, como siempre lo hace con los
strings constantes (esto es por lo que generalmente no necesitas ponerlos tu mismo).
Los NULLs en este string lo dividen en filtros, donde cada uno tiene dos partes. El
primer filtro tiene la descripcin "Text Files (*.txt)", la parte con la especificacin

(*.txt) no es necesaria aqu. La segunda parte es la especificacin real del primer


filtro, "*.txt". Hacemos lo mismo con el segundo filtro, excepto que es un filtro
genrico para todos los archivos. Podemos agregar tantos filtros como deseemos.
El campo lpstrFile apunta al bffer que hemos asignado para almacenar el nombre
del archivo, debido a que la longitud del nombre del archivo no puede ser mayor
que MAXPATH, ste es el valor que escogeremos para el tamao de dicho bffer.
Los flags indican que el dilogo slo debe permitir al usuario ingresar nombres de
archivos que ya existen (esto es porque queremos abrirlos, no crearlos) y para ocultar
la opcin de abrirlos en modo de slo lectura, opcin que no queremos proveer.
Finalmente proveemos una extensin por default, por lo tanto si el usuario
tipea "foo" y el archivo no es encontrado, intentaremos abrir"foo.txt".
Cuando seleccionamos un archivo para guardar en lugar de abrir, el cdigo es bastante
parecido, excepto que llamamos a GetSaveFile( ) y adems necesitamos cambiar los
flags para poner opciones mas apropiadas al guardar archivos.
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
OFN_OVERWRITEPROMPT;
En este caso no necesitamos que el archivo exista, pero si necesitamos que el
directorio exista debido a que no vamos a crearlo si no existe. Adems, si el archivo
existe, preguntamos al usuario si est seguro que desea sobreescribirlo.
NOTA: MSDN establece lo siguiente para el campo lStructSize:
lStructSize
Especifica la longitud de la estructura en bytes.
Windows NT 4.0: En una aplicacin que es compilada con WINVER y
_WIN32_WINNT >= 0x0500, usar para este campo el valor
OPENFILENAME_SIZE_VERSION_400.
Windows 2000/XP: Usar para este parmetro el valor OPENFILENAME.
Bsicamente, esto significa que, como Windows 2000 ha agregado algunos miembros a
esta estructura, su tamao ha cambiado. Si el cdigo anterior no funciona,
posiblemente sea porque probablemente no coincidan el tamao que tu compilador usa
y el tamao que tu sistema operativo espera (Windows 98, Windows NT4), por lo tanto
la llamada falla. Si esto sucede, intenta usarOPENFILENAME_SIZE_VERSION_400 en
lugar de sizeof(ofn). Gracias a las personas que me han notificado esto.

Lectura y Escritura de Archivos


En windows tenemos algunas opcciones para especificar de que manera queremos
acceder a los archivos. Podemos usar la vieja librera io.h open()/read()/write(), o
podemos usar la librerastdio.h fopen()/fread()/fwrite(), y por ltimo si usamos
C++ podemos usar iostreams.

Sin embargo, en Windows, todos stos mtodos finalmente llaman a las funciones de
la API Win32, las cuales usaremos aqu. Si ests utilizando otro mtodo para la
entrada/salida de archivos, entonces te resultar fcil entender el mtodo que vamos a
ver.
Para abrir archivos, podemos usar OpenFile( ) o CreateFile( ). MS recomienda
usar solamente CreateFile( ) debido a que OpenFile( ) es "obsoleta". CreateFile(
) es una funcim mucho ms verstil y provee un gran control sobre la forma en que
abrimos nuestros archivos.

Lectura
Digamos, por ejemplo, que le has permitido al usuario seleccionar un archivo
usando GetOpenFileName( )...
BOOL LoadTextFileToEdit(HWND hEdit, LPCTSTR pszFileName)
{
HANDLE hFile;
BOOL bSuccess = FALSE;
hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
DWORD dwFileSize;
dwFileSize = GetFileSize(hFile, NULL);
if(dwFileSize != 0xFFFFFFFF)
{
LPSTR pszFileText;
pszFileText = GlobalAlloc(GPTR, dwFileSize + 1);
if(pszFileText != NULL)
{
DWORD dwRead;
NULL))

if(ReadFile(hFile, pszFileText, dwFileSize, &dwRead,


{

pszFileText[dwFileSize] = 0; // Add null terminator


if(SetWindowText(hEdit, pszFileText))
bSuccess = TRUE; // funcion!

}
GlobalFree(pszFileText);
}
}
CloseHandle(hFile);

}
return bSuccess;

Hay una funcin completa para leer un archivo de texto dentro de un control edit. Esta
funcin, toma como parmetro el handle al control edit y el nombre del archivo a leer.
Adems esta particular funcin tiene un buen chequeo de errores, debido a que la

entrada/salida de archivos est expuesta a una gran cantidad de ellos y por lo tanto,
necesitamos chequearlos.
Nota: No usaremos la variable dwRead, excepto como prmetro en ReadFile( ). Este
parmetro DEBE ser provisto, de no ser as la llamada fallar.
En la llamada a CreateFile( ), GENERIC_READ significa que queremos acceso de slo
lectura. FILE_SHARE_READ significa que otros programas pueden abrir el archivo al
mismo tiempo que nosotros lo hacemos, pero slo si quieren leerlo, no queremos que
escriban en el archivo mientras estamos leyendo. Por ltimo, OPEN_EXISTING significa
que slo abriremos el archivo si existe, no queremos crearlo.
Una vez que hemos abierto el archivo y hemos chequeado si CreateFile( ) se
ejecut con xito, chequeamos el tamao del archivo para averiguar cuanta memoria
necesitamos reservar para poder leer el archivo entero. Cuando reservamos dicha
memoria, chequeamos para asegurarnos que se hizo con xito, y luego llamamos
a ReadFile( ) para cargar el contenido del disco dentro del bffer en memoria. Las
funciones de la API para archivos no entienden sobre Archivos de Texto, por lo tanto
no proveen mecanismos para leer una linea de texto, o agregar terminadores NULL al
final de nuestros strings. Esta es la razn por la que hemos asignado un byte extra,
despus de leer el archivo agregamos el terminador NULL para que luego podamos
pasar el bffer de memoria como si fuera un string a SetWindowsText( ).
Una vez que todo esto se ha ejecutado con xito, ponemos en nuestra variable
booleana bSucces (que indica si la operacin se ha ejecutado con xito) el valor TRUE.
Por ltimo, liberamos el bffer de memoria y cerramos el archivo.

Escritura
BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName)
{
HANDLE hFile;
BOOL bSuccess = FALSE;
hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
DWORD dwTextLength;
dwTextLength = GetWindowTextLength(hEdit);
// No need to bother if there's no text.
if(dwTextLength > 0)
{
LPSTR pszText;
DWORD dwBufferSize = dwTextLength + 1;
pszText = GlobalAlloc(GPTR, dwBufferSize);
if(pszText != NULL)
{
if(GetWindowText(hEdit, pszText, dwBufferSize))
{
DWORD dwWritten;

if(WriteFile(hFile, pszText, dwTextLength,

&dwWritten, NULL))

bSuccess = TRUE;
}
GlobalFree(pszText);

}
}
CloseHandle(hFile);
}
return bSuccess;
}

Es muy similar a leer archivos, pero con algunos cambios. Primero que todo, cuando
llamamos a CreateFile( ) especificamos que queremos acceso de lectura (Read), que
el archivo debera simpre ser creado nuevamente (y si existe debera ser borrado
cuando es abierto) y si no existe que sea creado con los atributos de los archivos
normales.
A continuacin obtenemos del control edit, la longitud necesaria del bffer de memoria,
debido a que ste es la fuente de los datos. Una vez que hemos asignado la memoria,
solicitamos el string al control edit usando GetWindowText( ) y luego lo escribimos al
achivo con WriteFile( ). Nuevamnete, al igual que con ReadFile( ), es necesario el
parmetro que retorna cuanto fu escrito, an cuando no lo usemos.

Aplicacin Parte 3: Barras de


Herramientas y Barras de
Estado
Ejemplo: app_three

Una Palabra IMPORTANTE


en los Controles Comunes
Al igual que con todos los controles
comunes, debemos llamar a
InitCommonControl ANTES de intentar
usarlos. Necesitaremos #include
<commctrl.h>para poder usar esta
funcin y para obtener la funciones y
declaraciones necesarias para el uso de
los controles comunes. Tambin
necesitaremos agregar comctl32.lib en
la configuracin de nuestro enlazador
(linker), si no se encuentra configurado de esa manera. Observa
queInitCommonControls( ) es una vieja API, para obtener mas control podemos
usar InitCommonControlsEx( ) la cual es requerida por los ms recientes controles

comunes. Sin embargo, debido a que no voy a usar ninguna de las caractersticas
avanzadas, InitCommonControls( ) ser adecuado y mas simple.

Barras de Herramientas
Podemos crear una barra de herramientas usando CreateToolbarEx( ) pero no es la
idea... Lo que necesitamos hacer es crear la barra de herramientas...
hTool = CreateWindowEx(0, TOOLBARCLASSNAME, NULL, WS_CHILD |
WS_VISIBLE, 0, 0, 0, 0,
hwnd, (HMENU)IDC_MAIN_TOOL, GetModuleHandle(NULL), NULL);
Esto es bastante simple, TOOLBARCLASSNAME es una constante definida por los
encabezados (headers) de los controles comunes. hwnd es la ventana padre, la ventana
en la que aparecer la barra de herramientas. IDC_MAIN_TOOL es un identificador que
podemos usar posteriormente para obtener el HWND de la barra de herramientas por
medio de GetDlgItem( ).
// Send the TB_BUTTONSTRUCTSIZE message, which is required for
// backward compatibility.
SendMessage(hTool, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON),
0);
Este mensaje es necesario para permitirle al sistema darse cuenta que versin de la
librera de controles comunes estamos usando. Debido a que nuevas versiones agregan
cdigo a la estructura, dando el tamao de la misma el sistema puede darse cuenta
que comportamiento estamos esperando.

Botones de la barra de Herramientas


Los botones con bitmaps para las barras de herramientas vienen en dos variedades, los
botones estndar que son provistos por comctl32 y los botones definidos por el
usuario, que son creados por l mismo. NOTA: Los botones y bitmaps son agregados a
las barras de herramientas en forma separada... primero agregamos una lista de
imgenes a usar y luego agregamos la lista de botones. Por ltimo especificamos que
imagen usar en cada botn.

Agregado de Botones Estndar


Ahora que hemos creado una barra de herramientas, necesitamos agregarle algunos
botones. Los bitmaps mas comunes estn disponibles dentro de la librera de los
controles comunes, por lo tanto no necesitamos crearlos o agregarlos en cada
archivo .exe que los usa.
Primero declaramos TBBUTTON y TBADDBITMAP
TBBUTTON tbb[3];
TBADDBITMAP tbab;

y luego agregamos los los bitmaps estndar a la barra de herramientas usando la lista
de imgenes en la libreria de los controles comunes...
tbab.hInst = HINST_COMMCTRL;
tbab.nID = IDB_STD_SMALL_COLOR;
SendMessage(hTool, TB_ADDBITMAP, 0, (LPARAM)&tbab);
Ahora que hemos cargado nuestras imgenes, podemos agregar algunos botones que
las utilicen...
ZeroMemory(tbb, sizeof(tbb));
tbb[0].iBitmap = STD_FILENEW;
tbb[0].fsState = TBSTATE_ENABLED;
tbb[0].fsStyle = TBSTYLE_BUTTON;
tbb[0].idCommand = ID_FILE_NEW;
tbb[1].iBitmap =
tbb[1].fsState =
tbb[1].fsStyle =
tbb[1].idCommand

STD_FILEOPEN;
TBSTATE_ENABLED;
TBSTYLE_BUTTON;
= ID_FILE_OPEN;

tbb[2].iBitmap =
tbb[2].fsState =
tbb[2].fsStyle =
tbb[2].idCommand

STD_FILESAVE;
TBSTATE_ENABLED;
TBSTYLE_BUTTON;
= ID_FILE_SAVEAS;

SendMessage(hTool, TB_ADDBUTTONS, sizeof(tbb)/sizeof(TBBUTTON),


(LPARAM)&tbb);
Aqu hemos agregado los botones New, Open y Save As usando las imgenes estndar,
lo cual es una buena idea debido a que las personas suelen verlas y ya saben que
significan.
Los ndices de las lista de imgenes estn definidos en los encabezados de los
controles comunes y tambin son listados en MSDN.
Hemos asignado a cada botn un ID (ID_FILE_NEW, etc...) lo cual es idntico a los IDs
de los items equivalentes en el Men. Estos botones generarn
mensajes WM_COMMAND identicos a los del Men, por lo tanto no se requiere algn
procesamiento extra! Si fueramos a agregar un botn para un comando que no
aparece en el Men, simplemente definimos un nuevo ID y agregamos un manejador
a WM_COMMAND.
Si te ests preguntando por qu he pasado wParam a TB_ADDBUTTONS, bueno, est
realizando un clculo del nmero de botones en el arreglo tbb para que no necesitemos
especificar un valor. Si en lugar de esto, lo que hacemos es poner un 3, bien, estara
correcto pero que sucede si queremos agregar otros botones... deberamos cambiar
este numero por 4 y en programacin eso no es muy bueno... queremos que los
cambios provoquen la menor cantidad de cambios. Por ejemplo, si el tamao
de TBBUTON tiene 16 bits, debido a que tenemos 3 botones el tamao del tbb debera
ser de 16*3 = 48. Del mismo modo, 48/16 da el nmero de botones, 3 en este caso.

Barras de Estado

Algo que se suele encontrar a menudo en las aplicaciones son las barras de estado, la
pequea barra en la parte inferior de la ventana que muestra informacin. Son muy
fcil de usar, solo hay que crearlas...
hStatus = CreateWindowEx(0, STATUSCLASSNAME, NULL,
WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0,
hwnd, (HMENU)IDC_MAIN_STATUS, GetModuleHandle(NULL), NULL);
Y luego (opcionalmente) especificamos el numero de secciones que deseamos. Si no
especificamos ninguna, simplemente tendr una que usa el ancho entero de la barra.
Podemos especificar o recuperar el texto usando SetWindowText( ) como con lo
hacamos con muchos de los otros controles. Si especificamos ms de una seccin,
necesitamos especificar el ancho de cada una y luego usar SB_SETTEXT para especificar
el texto de cada seccin.
Para definir los anchos declaramos un arreglo de enteros donde cada valor es el ancho
en pixeles de una seccin. Si queremos que una seccin use el espacio restante
fijamos como valor -1.
El wParam nuevamente es para calcular cuantos elementos hay en el arreglo. Una vez
que hemos terminado de agregar secciones, fijamos el primer valor (de ndice 0) para
verla en accin.

Tamao Apropiado
A diferencia de los Menes, las barras de estado y de herramientas son controles
separados que viven dentro del rea cliente de la ventana padre. Por lo tanto si solo
dejamos nuestro cdigoWM_SIZE anterior, se van a solapar con el control edit que
hemos agregado en los ejemplos anteriores. En WM_SIZE, ponemos las barras de
estado y de herramientas en posicin y luego restamos el alto del area cliente para que
podamos mover nuestro control edit para rellenar el espacio restante...
HWND hTool;
RECT rcTool;
int iToolHeight;
HWND hStatus;
RECT rcStatus;
int iStatusHeight;
HWND hEdit;
int iEditHeight;
RECT rcClient;
// Size toolbar and get height
hTool = GetDlgItem(hwnd, IDC_MAIN_TOOL);
SendMessage(hTool, TB_AUTOSIZE, 0, 0);
GetWindowRect(hTool, &rcTool);
iToolHeight = rcTool.bottom - rcTool.top;
// Size status bar and get height
hStatus = GetDlgItem(hwnd, IDC_MAIN_STATUS);
SendMessage(hStatus, WM_SIZE, 0, 0);

GetWindowRect(hStatus, &rcStatus);
iStatusHeight = rcStatus.bottom - rcStatus.top;
// Calculate remaining height and size edit
GetClientRect(hwnd, &rcClient);
iEditHeight = rcClient.bottom - iToolHeight - iStatusHeight;
hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
SetWindowPos(hEdit, NULL, 0, iToolHeight, rcClient.right,
iEditHeight, SWP_NOZORDER);
Desafortunadamente esta es una porcin de cdigo bastante larga, pero es simple...
las barras de herramiemntas se autoposicionarn cuando envemos el
mensaje TB_AUTOSIZE y las barras de estado harn lo mismo cuando
enviemos WM_SIZE (las libreras de los controles comunes no entienden nada a cerca
de consistencia).

Aplicacin Parte 4: Interfaces


con Mltiples Documentos
Ejemplo: app_four

Introduccin a IMD
Primero un poco de repaso... Todas las
ventanas tienen un Area Cliente, aqu es donde
la mayora de los programas dibujan imgenes,
ubican los controles, etc... el Area Cliente no
est separada de la ventana, simplemente es
una pequea regin especializada de la
ventana. A veces una ventana puede ser todo
el rea cliente, y veces nada, a veces el rea
cliente puede hacerse mas pequea par caber
en menes, ttulos, barras de desplazamiento,
etc...
En trminos de IMD (en ingls, MDI - Multiple Document Interface), nuestra ventana
principal es llamada Frame y sta es probablemente la nica ventana que podramos
tener en un programa IUD (Interface con un Unico Documento). En IMD hay una
ventana adicional llamada Ventana Cliente IMD, la cual es hija de nuestro Frame y a
diferencia del Area Cliente, es una ventana completa y separada de las dems que
tiene un rea cliente y probablemente algunos pixeles para un borde. Nunca
procesaremos mensajes directamente del Cliente IMD, esto es hecho por la clase
ventana pre-definida "MDI_CLIENT". Podemos comunicarnos y manipular el rea cliente
IMD, como as tambin las ventanas que sta contiene, atravs de mensajes.

Cuando entramos a la ventana que muestra nuestro documento, o lo que sea que
muestre el programa, envamos un mensaje al Cliente IMD para decirle que cree una
nueva ventana del tipo que hemos especificado. La nueva ventana es creada como una
ventana hija del Cliente IMD, no de nuestro Frame Window. Esta nueva ventana es una
hija IMD (IMD Child). La hija IMD Child es hija del Cliente IMD, el cual a su vez, es hijo
del Frame IMD... La Hija IMD probablemente tenga sus propias ventanas hijas, por
ejemplo el control edit en el programa del ejemplo de esta seccin.
Tenemos que escribir dos (o mas) Window Procedures. Uno, como siempre, para
nuestra ventana principal (el Frame) y uno ms para la Hija IMD. Podemos tener
tambin mas de un tipo de hija, en cuyo caso escribiremos window procedures
separados para cada tipo.
Si te he confundido hablando de Clientes IMD y todo eso, quizs este diagrama puede
ayudarte a aclarar un poco las cosas.

Ahora si, IMD


IMD requiere algunos cambios astutos a lo largo de un programa, por lo tanto lee esta
seccin cuidadosamente... si tu programa no funciona o tiene un comportamiento

extrao es porque erraste alguna de las alteraciones que vamos a hacer al programa
regular.

Ventana Cliente IMD


Antes de que creemos nuestra ventana IMD necesitamos hacer un cambio al
procesamiento por default que utilizamos en nuestro Window Procedure... debido a que
estamos creando un Frame que residir en un Cliente IMD, necesitamos cambiar la
llamada a DefWindowProc( ) a DefFrameProc( ) la cual agrega un procesamiento
especializado de mensajes para Frames.
default:
return DefFrameProc(hwnd, g_hMDIClient, msg, wParam, lParam);
El prximo paso es crear la ventana Cliente IMD, como una hija de nuestro Frame. Esto
lo hacemos en el WM_CREATE, como antes...
CLIENTCREATESTRUCT ccs;
ccs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 2);
ccs.idFirstChild = ID_MDI_FIRSTCHILD;
g_hMDIClient = CreateWindowEx(WS_EX_CLIENTEDGE, "mdiclient", NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL |
WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hwnd, (HMENU)IDC_MAIN_MDI, GetModuleHandle(NULL), (LPVOID)&ccs);
El menu handle es el handle al men desplegable en el que el Cliente IMD agregar
items para representar cada ventana que es creada, permitindole al usuario elegir
desde el men la ventana que quiere activar. Por lo tanto, agregaremos funcionalidad
para manejar este caso. En este ejemplo, es la tercera (de ndice 2) debido a que la he
agregado al Men despus de File, Edit y Window.
ccs.idFirstChild es el nmero para usar como el primer ID para los items que el
Cliente agrega a la Ventana men... queremos que esto sea fcilmente distinguible de
nuestros identificadores de menes para que podamos procesar los comandos del
men y pasarlos desde la ventana a DefFrameProc( ) para que los procese. En el
ejemplo he especificado un identificador definido como50000, que es lo suficientemente
grande para que ninguno de los identificadores de los comandos del men sean mayor
que ste.
Ahora para que este men funcione apropiadamente, necesitamos agregar algn
procesamiento especial a nuestro manejador del mensaje WM_COMMAND:
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_FILE_EXIT:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
// ... handle other regular IDs ...
// Handle MDI Window commands
default:
{

if(LOWORD(wParam) >= ID_MDI_FIRSTCHILD)


{
DefFrameProc(hwnd, g_hMDIClient, msg, wParam,
lParam);

}
else
{

HWND hChild = (HWND)SendMessage(g_hMDIClient,


WM_MDIGETACTIVE,0,0);
if(hChild)
{
SendMessage(hChild, WM_COMMAND, wParam, lParam);
}
}
}
}
break;
He agregado en el case un caso ms, el default:, el cual atrapar todos los
comandos que no procesemos directamente y realizar un chequeo para ver si el valor
es mayor o igual aID_MDI_FIRSTCHILD. Si lo es, entonces el usuario ha clickeado en
uno de los items del men de la ventana y envamos el mensaje
a DefFrameProc( ) para que lo procese.
Si no es un alguno de los IDs de la ventana, entoces obtenemos el handle de la
ventana hijo que est activa y le enviamos el mensaje para que lo procese. Esto nos
permite delegar la responsabilidad de realizar ciertas acciones a la ventana Hija y nos
permite que diferentes ventanas Hijas procesen comandos de diferentes formas, si as
se desea. En el ejemplo, en el Frame window procedure solo proceso los comandos que
son globales al programa y envo los comandos que afectan a cierto documento o una
ventana Hija hacia dicha ventana Hija para que los procese.
Tambin necesitamos modificar un poco nuestro Loop de Mensajes...
while(GetMessage(&Msg, NULL, 0, 0))
{
if (!TranslateMDISysAccel(g_hMDIClient, &Msg))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
Hemos agregado un paso extra (TranslateMDISysAccel()), que chequea por el
acelerador de teclas pre-definido, Ctrl+F6 cambia a la siguiente ventana, Ctrl+F4 cierra
la ventana Hijo, etc... Si no quieres agregar este chequeo impedirs a los usuarios de
usar el comportamiento estndar que estn acostumbrados a usar, o tendrs que
implementarlo manualmente.

Clase Ventana Hija


Adems de la ventana principal de un programa (el Frame), necesitamos crear una
nueva clase ventana para cada tipo de ventana hija que necesitamos. Por ejemplo,
podemos tener una que muestre texto y otra para mostrar grficos o fotos. En este

ejemplo solo crearemos un tipo de hijo, el cual ser como el editor de los ejemplos
anteriores.
BOOL SetUpMDIChildWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wc;
wc.cbSize
wc.style
wc.lpfnWndProc
wc.cbClsExtra
wc.cbWndExtra
wc.hInstance
wc.hIcon
wc.hCursor
wc.hbrBackground
wc.lpszMenuName
wc.lpszClassName
wc.hIconSm

=
=
=
=
=
=
=
=
=
=
=
=

sizeof(WNDCLASSEX);
CS_HREDRAW | CS_VREDRAW;
MDIChildWndProc;
0;
0;
hInstance;
LoadIcon(NULL, IDI_APPLICATION);
LoadCursor(NULL, IDC_ARROW);
(HBRUSH)(COLOR_3DFACE+1);
NULL;
g_szChildClassName;
LoadIcon(NULL, IDI_APPLICATION);

if(!RegisterClassEx(&wc))
{
MessageBox(0, "Could Not Register Child Window", "Oh Oh...",
MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}
else
return TRUE;
}
Esto es bsicamente idntico a registrar nuestro Frame, no hay flags particularmente
especiales para usar con IMD. Debemos poner nuestro men en NULL y el window
procedure apuntando al window procedure de la ventana hija que escribiremos a
continuacin.

IMD Procedure Hijo


El window procedure para una IMD Hija es parecido a culaquier otro pero con algunas
excepciones. Primero de todo, los mensajes por default, en lugar de ser pasados
a DefWindowProc( ), son pasados a DefMDIChildProc( ).
En este caso particular, tambin queremos deshabilitar los menes Edit y Window
cuando no se necesitan (slo porque es bueno hacerlo), por lo tanto procesando el
mensaje WM_MDIACTIVATE los habilitamos o deshabilitamos dependiendo si la ventana
est activa o no. Si tenemos mltiples tipos de ventana hijo, aqu es donde podemos
poner el cdigo para cambiar completamente el men, la barra de herramientas o
hacer alteraciones a otros aspectos del programa para reflejar las acciones y comandos
que son especficos del tipo de ventana que est siendo activada.
Para ser an mas completos, podemos deshabilitar los items Close y Open del men,
debido a que no sern de utilidad cuando las ventanas no estn activas. He
deshabilitado todos estos items por default en el recurso, por lo tanto no necesitamos
agregar cdigo extra para hacer esto cuando la aplicacin se ejecuta por primera vez.

LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT msg, WPARAM wParam,


LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
HFONT hfDefault;
HWND hEdit;
// Creamos el control Edit
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL |
ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
0, 0, 100, 100, hwnd, (HMENU)IDC_CHILD_EDIT,
GetModuleHandle(NULL), NULL);
if(hEdit == NULL)
MessageBox(hwnd, "Could not create edit box.", "Error",
MB_OK | MB_ICONERROR);
hfDefault = GetStockObject(DEFAULT_GUI_FONT);
SendMessage(hEdit, WM_SETFONT, (WPARAM)hfDefault,
MAKELPARAM(FALSE, 0));
}
break;
case WM_MDIACTIVATE:
{
HMENU hMenu, hFileMenu;
UINT EnableFlag;
hMenu = GetMenu(g_hMainWindow);
if(hwnd == (HWND)lParam)
{
//si est activa, activamos los menus
EnableFlag = MF_ENABLED;
}
else
{
//cuando se desactiva,
desactivamos los menus
EnableFlag = MF_GRAYED;
}
EnableMenuItem(hMenu, 1, MF_BYPOSITION | EnableFlag);
EnableMenuItem(hMenu, 2, MF_BYPOSITION | EnableFlag);
hFileMenu = GetSubMenu(hMenu, 0);
EnableMenuItem(hFileMenu, ID_FILE_SAVEAS, MF_BYCOMMAND |
EnableFlag);
EnableMenuItem(hFileMenu, ID_FILE_CLOSE, MF_BYCOMMAND |

EnableFlag);

EnableMenuItem(hFileMenu, ID_FILE_CLOSEALL, MF_BYCOMMAND |

EnableFlag);

DrawMenuBar(g_hMainWindow);
}
break;

case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_FILE_OPEN:
DoFileOpen(hwnd);
break;
case ID_FILE_SAVEAS:
DoFileSave(hwnd);
break;
case ID_EDIT_CUT:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_CUT, 0,
0);

break;
case ID_EDIT_COPY:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_COPY, 0,

0);

0);

break;
case ID_EDIT_PASTE:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_PASTE, 0,
break;
}
break;
case WM_SIZE:
{
HWND hEdit;
RECT rcClient;
GetClientRect(hwnd, &rcClient);

hEdit = GetDlgItem(hwnd, IDC_CHILD_EDIT);


SetWindowPos(hEdit, NULL, 0, 0, rcClient.right,
rcClient.bottom, SWP_NOZORDER);
}
return DefMDIChildProc(hwnd, msg, wParam, lParam);
default:
return DefMDIChildProc(hwnd, msg, wParam, lParam);

}
return 0;

He implementado como comandos File Open y


Save, DoFileOpen( ) y DoFileSave( ) son igual que en los ejemplos anteriores con el
ID del control edit cambiado y adicionalmente he puesto como ttulo de la Ventana Hija
IMD el nombre del archivo.
Los comandos edit son fciles, debido a que el control edit est desarrollado para
soportarlos, slo le decimos que hacer.
Recuerdas que he mencionado que hay algunas cosas que necesitas recordar o tu
aplicacin se comportar de manera extraa? Observa que he
llamado DefMDIChildProc( ) al final del WM_SIZE, esto es importante porque de no
ser as, el sistema no tendr la chance de hacer su propio procesamiento sobre el

mensaje. Puedes buscar sobre DefMDIChildProc( ) en MSDN para encontrar una lista
sobre los mensajes que procesa y siempre asegurarte de pasrselos.

Crear y Destruir Ventanas


Las ventanas Hijas IMD nos son creadas directamente, al contrario, le enviamos a la
ventana cliente el mensaje WM_MDICREATE dicindole que tipo de ventana queremos
crear rellenando los miembros de una estructura MDICREATESTRUCT. Puedes ver los
distintos miembros de sta estructura en la documentacin. El valor de retorno del
mensaje WM_MDICREATE es el handle a la nueva ventana.
HWND CreateNewMDIChild(HWND hMDIClient)
{
MDICREATESTRUCT mcs;
HWND hChild;
mcs.szTitle = "[Untitled]";
mcs.szClass = g_szChildClassName;
mcs.hOwner = GetModuleHandle(NULL);
mcs.x = mcs.cx = CW_USEDEFAULT;
mcs.y = mcs.cy = CW_USEDEFAULT;
mcs.style = MDIS_ALLCHILDSTYLES;

hChild = (HWND)SendMessage(hMDIClient, WM_MDICREATE, 0, (LONG)&mcs);


if(!hChild)
{
MessageBox(hMDIClient, "MDI Child creation failed.", "Oh Oh...",
MB_ICONEXCLAMATION | MB_OK);
}
return hChild;

Un miembro de la estructura MDICREATESTRUCT que no usamos pero puede ser muy


til es el lParm. Este puede ser usado para enviar cualquier valor de 32 bits (un
puntero) a la ventana hija que ests creando con el propsito de proveerle cualquier
informacin. En el manejador WM_CREATE de nuestra ventana hija, el valor lParam para
el mensaje WM_CREATE apuntar a la estructuraMDICREATESTRUCT. El
miembro lpCreateParams de dicha estructura apuntar a MDICREATESTRUCT que
enviamos junto con WM_MDICREATE. Por lo tanto para acceder al valor lparam de la
ventana hija necesitamos hacer algo como esto en el window procedure de dicha
ventana...
case WM_CREATE:
{
CREATESTRUCT* pCreateStruct;
MDICREATESTRUCT* pMDICreateStruct;
pCreateStruct = (CREATESTRUCT*)lParam;
pMDICreateStruct = (MDICREATESTRUCT*)pCreateStruct>lpCreateParams;
/*
pMDICreateStruct now points to the same MDICREATESTRUCT that you
sent along with the WM_MDICREATE message and you can use it

to access the lParam.


*/

}
break;

Si no quieres complicarte con esos dos punteros extras, puedes acceder a lParam en
un solo paso de la siguiente manera: ((MDICREATESTRUCT*)
((CREATESTRUCT*)lParam)->lpCreateParams)->lParam
Ahora podemos implementar los comandos File del men en nuestro Frame window
procedure:
case ID_FILE_NEW:
CreateNewMDIChild(g_hMDIClient);
break;
case ID_FILE_OPEN:
{
HWND hChild = CreateNewMDIChild(g_hMDIClient);
if(hChild)
{
DoFileOpen(hChild);
}
}
break;
case ID_FILE_CLOSE:
{
HWND hChild = (HWND)SendMessage(g_hMDIClient,
WM_MDIGETACTIVE,0,0);
if(hChild)
{
SendMessage(hChild, WM_CLOSE, 0, 0);
}
}
break;
Tambin podemos proveer algn procesamiento IMD por default al ordenamiento de la
ventana para nuestro Window Men, debido a que IMD soporta esto, no es mucho
trabajo.
case ID_WINDOW_TILE:
SendMessage(g_hMDIClient, WM_MDITILE, 0, 0);
break;
case ID_WINDOW_CASCADE:
SendMessage(g_hMDIClient, WM_MDICASCADE, 0, 0);
break;

Anda mungkin juga menyukai