Anda di halaman 1dari 90

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 funcin GetModuleFileName( ) 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 a MessageBox() 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 = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

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

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

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

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

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


{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
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 0x0110
#define WM_COMMAND 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 mensaje WM_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 el HWND (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 a DefWindowProc( ) 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 de DispatchMessage( ). 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 un BOOL (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
cuando GetMessage( ) 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. Pero IDI_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 101

#define ID_FILE_EXIT 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

POPUP "&Stuff"
BEGIN
MENUITEM "&Go", ID_STUFF_GO
MENUITEM "G&o somewhere else", 0, GRAYED
END
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 a DialogBox().

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 que DialogBox() 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 FALSE en 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 existe GetWindowTextLenght( ), 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 en LBN_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 llamada SetWindowPos( ) 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 con NULL 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 usar OPENFILENAME_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 librera stdio.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;
if(ReadFile(hFile, pszFileText, dwFileSize, &dwRead,
NULL))
{
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 que
InitCommonControls( ) 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 = STD_FILEOPEN;
tbb[1].fsState = TBSTATE_ENABLED;
tbb[1].fsStyle = TBSTYLE_BUTTON;
tbb[1].idCommand = ID_FILE_OPEN;

tbb[2].iBitmap = STD_FILESAVE;
tbb[2].fsState = TBSTATE_ENABLED;
tbb[2].fsStyle = TBSTYLE_BUTTON;
tbb[2].idCommand = 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
cdigo WM_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 como 50000, 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 a
ID_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 = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MDIChildWndProc;
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_3DFACE+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szChildClassName;
wc.hIconSm = 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);
break;
case ID_EDIT_PASTE:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_PASTE, 0,
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 estructura MDICREATESTRUCT. 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;

Bitmaps, Dispositivos de Contexto y BitBlt


Ejemplo: bmp_one

GDI
Una cosa buena que tiene MS Windows, a diferencia de DOS, es que para mostrar grficos
no necesitamos conocer nada acerca del hardware de video que estamos usando. Por el
contrario, Windows provee una API llamada Graphics Device Interface (Interface del
Dispositivo Grfico) o GDI, en forma abreviada. LA GDI usa un conjunto de objetos
grficos genricos que pueden ser usados para dbujar en la pantalla, en la memoria, o en la
impresora.

Dispositivo de Contextos

La GDI gira entorno a un objeto llamado Dispositivo de Contexto (DC), representado por el
tipo de dato HDC (Handle al Dispositivo de Contexto). Un HDC es bsicamente un handle a
algo en lo que podemos dibujar, puede representar la pantalla entera, una ventana completa,
el rea cliente de una ventana, un bitmap almacenado en memoria o puede ser una
impresora. La parte buena es que no necesitamos saber a cul de todas estas cosas hace
referencia, se usa bsicamente de la misma manera, lo cual es muy til para escribir
funciones de dibujo que podemos usar en cualquiera de estos dispositivos sin necesidad de
cambiarla para cada uno de estos.

Un HDC, al igual que la mayora de los objetos GDI, es opaco. Esto significa que no
podemos acceder a sus datos de forma directa... pero podemos pasrselo a varias funciones
GDI que operarn en sus datos, ya sea para dibujar algo, para obtener informacin a cerca
de ste, o para cambiar de alguna manera el objeto.

Por ejemplo, si queremos dibujar en una ventana, primero debemos obtener un HDC que
represente la ventana, para esto utilizamos GetDC( ). Luego podemos usar cualquiera de
las funciones GDI que toman un HDC, como por ejemplo BitBlt( ) para dibujar
imgenes, TextOut( ) para dibujar texto, LineTo( ) para lineas, etc...

Bitmaps

Los bitmaps puden ser cargados en su mayora como los conos de los primeros ejemplos.
Hay una funcin LoadBitmap( ) para simplemente cargar un recurso bitmap y una funcin
LoadImage( ) para cargar bitmaps desde cualquier archivo *.bmp.

Una de las sutilezas de la GDI es que no podemos dibujar directamente sobre objetos
bitmap (HBITMAP). Recuerda que las operacioens de dibujo son abstractas y dicha
abstraccin la realiza el Dispositivo de Contextos, por lo tanto para usar cualquiera de estas
funciones de dibujo sobre un bitmap necesitamos crear un Memory DC y luego seleccionar
el HBITMAP dentro de ste usando SelectObject( ). El efecto es que el "dispositvo" al
cual el HDC hace referencia es el bitmap en memoria y cuando operamos sobre el HDC los
resutados de las funcioens grficas son aplicados al bitmap. Como vimos, esto es una forma
muy conveniente de hacer las cosas porque podemos escribir cdigo que dibuja sobre un
HDC y luego usarlo sobre un Window DC o sobre un Memory DC sin tener que realizar
cambios o chequeos.

Tenemos la opcin de manipular nosotros mismos los datos del bitmap en memoria.
Podemos hacer esto con el Dispositivo de Bitmaps Independientes (DIB) y podemos an
combinar operaciones GDI y operaciones manuales en el DIB. Sin embargo, por el
momento, esto est mas all del alcance de este tutorial bsico y por ahora slo cubriremos
las operaciones GDI mas simples.

Liberacin de Objetos GDI

Una vez que hemos finalizado de usar un HDC es muy importante liberarlo (cmo lo
hacemos depende de cmo lo obtuvimos, hablaremos de esto dentro de un momento). Los
objetos GDI estn limitados en nmero. En versiones de Windows anteriores a Windows
95, no slo estaban increblemente limitados sin que tambin compartan ampliamente los
recursos del sistema, por lo tanto, si un programa usaba demasiados, ninguno de los dems
programas poda ser capaz de dibujar nada! Afortunadamente, en Windows 2000 o XP, esto
ya no sucede ms y podemos obtener una gran cantidad de recursos sin que suceda algo
malo... pero an as es fcil olvidarse de liberar objetos GDI y rpidamente podemos
obtener un agotamiento de los recursos en sistemas como Windows 9x. Tericamente no
deberamos poder agotar los recursos GDI en sistemas basados en tecnologa NT
(NT/2K/XP) pero puede llegar a suceder en casos extremos, o si acertamos en el bug
apropiado...

Si nuestro programa se ejecuta bien algunos minutos y luego comienza a dibujar


extraamente o de forma incompleta, es una buena seal de que ests reteniendo objetos
GDI. Los HDCs no son los nicos objetos con los que necesitamos ser cuidadosos con
respecto a su liberacin, en general es bueno mantener cosas como bitmaps y fuentes hasta
el final de la ejecucin del programa, debido a que es mucho ms eficiente que recargarlos
cada vez que los necesitamos.

Actualizacin Importante: Windows tiene asignados (retenidos) ciertos objetos por


default en los HDC, pero hay unos pocos objetos que nunca utiliza para esto y puedes
buscar cules son en MSDN. Debido a esto, tena la incertidumbre si HBITMAP era uno de
ellos, ya que parece no haber una documentacin definitiva sobre ste y los ejemplos (an
los de Microsoft) suelen ignorar el bitmap por default. De la escritura del tutorial original
hace varios aos, me han confirmado que hay un bitmap que necesita ser liberado. Esta
informacin es cortesa de Shaun Ivory, un ingeniero de software de MS y un amigo mio de
#winprog.

Aparentemente hay un bug en un protector de pantalla escrito por MS y esto se debe a que
el bitmap por default no fu reemplazado o destrudo y eventualmente provoca un desborde
de los recursos GDI. Debes estar atentos!, es un error fcil de cometer.

Como Mostrar Bitmaps


Ok, volvamos al tema. Las operaciones ms simple de dibujo sobre una ventana se realizan
procesando el mensaje WM_PAINT. Cuando nuestra ventana es mostrada por primera vez, o
es restaurada o minimizada, o es descubierta luego de haber estado tapada por otra ventana,
Windows le enva a la ventana el mensaje WM_PAINT para hacerle saber que necesita
redibujar su contenido. Cuando dibujamos algo sobre la pantalla NO ES permanente,
simplemente permanece hasta que algo se dibuja encima y luego, cuando se destapa, slo es
necesario redibujarlo.
HBITMAP g_hbmBall = NULL;
case WM_CREATE:
g_hbmBall = LoadBitmap(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDB_BALL));
if(g_hbmBall == NULL)
MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK |
MB_ICONEXCLAMATION);
break;
El primer paso, por supuesto, es cargar el bitmap. Esto es simple cuando tenemos un
recurso bitmap ya que no hay diferencias significantes de cuando cargamos otro tipo de
recursos.
case WM_PAINT:
{
BITMAP bm;
PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

HDC hdcMem = CreateCompatibleDC(hdc);


HBITMAP hbmOld = SelectObject(hdcMem, g_hbmBall);

GetObject(g_hbmBall, sizeof(bm), &bm);

BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0,


SRCCOPY);

SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);

EndPaint(hwnd, &ps);
}
break;

Obtener el Window DC

Para comenzar, declaramos algunas variables. Observa que la primera es un BITMAP y no un


HBITMAP. BITMAP es una estructura que almacena informacin acerca de un HBITMAP el cual
es el verdadero objeto GDI. Necesitamos una forma de obtener el alto y ancho del HBITMAP,
para esto usamos GetObject( ), el cual, contrariamente a su nombre, no obtiene un objeto
pero si obtiene informacin acerca de uno ya existente. "GetObjectInfo" podra haber sido
un nombre ms apropiado. GetObject( ) funciona con varios tipos de objetos GDI, que
puede distinguir usando el segundo parmetro, el tamao de la estructura.

PAINTSTRUCT es una estructura que contiene informacin acerca de la ventana que esta
siendo pintada, que es la que contina con el mensaje paint. Para las tareas ms simples,
podemos ignorar la informacin que esta estructura contiene, pero sta es necesaria en la
llamada a BeginPaint( ). BeginPaint( ) est diseada para procesar especifcamente el
mensaje WM_PAINT. Cuando no procesamos el mensaje WM_PAINT podemos usar GetDC( ),
el cual veremos en los ejemplos de la animacin dentro de un momento... pero en el
mensaje WM_PAINT es importante usar BeginPaint( ) y EndPaint( ).

BeginPaint( ) retorna un HDC que representa el HWND que le pasamos, el de la ventana que
est procesando el WM_PAINT. Cualquier operacin de dibujo que realizemos sobre este
HDC ser inmediatamente mostrada en la pantalla.

Preparando un Memory DC para el Bitmap


Como mencion anteriormente, para poder dibujar sobre bitmaps o con bitmaps,
necesitamos crear un DC en memoria... la forma ms fcil de hacerlo aqu es usando
CreateCompatibleDC( ) con el que ya tenemos. Con esto obtenemos un Memory DC que
es compatible con el color y las propiedades de dibujo del HDC de la ventana.

Ahora llamamos a SelectObject( ) para seleccionar el bitmap dentro del DC, siendo
cuidadosos de almacenar el bitmap por default para que podamos reemplazarlo
posteriormente y no retener objetos GDI.

Dibujo

Una vez que hemos llenado los valores ancho y alto dentro de la estructura BITMAP
podemos llamar a BitBlt( ) para copiar la imagen desde nuestro Memory DC hasta el
Window DC y luego mostrarlo en pantalla. Como siempre puedes buscar cada parmetro en
MSDN, pero ac estn resumidos: El destino, la posicin, el tamao, el origen y la posicin
origen y finalmente el Raster Operation (cdigo ROP), el cual especifica como hacer la
copia. En ese caso queremos una copia exacta del recurso, nada ms.

BitBlt probablemente sea la funcin mas feliz de toda la API Win32 y es la dieta pricipal de
todos aquellos que estn aprendiendo a escribir juegos o alguna otra aplicacin grfica en
Windows. Esta fu probablemente la primer API de la cual he memorizado todos los
parmetros.

Limpieza

Hasta este punto el bitmap debera estar en pantalla y es necesario que lo borremos nosotros
mismos. Lo primero que hay que hacer es restaurar el Memory DC al estado en el que
estaba cuando lo obtuvimos, para esto, reemplazamos nuestro bitmap con el bitmap por
default que habamos guardado. Luego podemos borrarlo por completo usando DeleteDC(
).

Finalmente liberamos el Window DC que obtuvimos con BeginPaint( ) usando


EndPaint( ).

Destruir un HDC a veces es un poco confuso debido a que hay al menos 3 formas de
hacerlo que dependen de como lo obtuvimos. Aqu hay una lista de los mtodos ms
comunes para obtener un HDC y luego liberarlo.

GetDC() - ReleaseDC()
BeginPaint() - EndPaint()
CreateCompatibleDC() - DeleteDC()

Por ltimo, al final de nuestro programa, liberamos todos los recursos que tenemos
asignados. Tcnicamente hablando, esto no es absolutamente necesario debido a que las
plataformas modernas de Windows son muy buenas liberando todos los recursos cuando
nuestro programa termina, aunque siempre es conveniente hacerlo uno mismo ya que hay
viejas versiones de Windows que no hacen esto de manera efectiva.

case WM_DESTROY:
DeleteObject(g_hbmBall);
PostQuitMessage(0);
break;

Bitmaps Transparentes
Ejemplo: bmp_two

Transparencia
Darles a los bitmaps la apariencia de tener secciones transparentes es bastante simple e
involucra el uso de una Mscara blanca y negra junto al color de la imagen que queremos
que luzca transparente.

Para que el efecto funcione apropiadamente se necesitan reunir las siguientes condiciones:
Primero la imagen debe ser de color negro en todas las reas que queremos que luzca
transparente. Segundo, La mscara debe ser blanca en las reas que queremos que
sean transparentes y negro en caso contrario. La imgen y la mscara son mostradas en la
figura includa como ejemplo en esta seccin y corresponden a las dos imgenes que se
encuentran alineadas mas a la izquierda.

Operaciones BitBlt

Como hace esto para proveernos transparencia? Primero usamos BitBlt( ) sobre la
mscara usando la operacin SRCAND como ltimo parmetro y luego encima de esto
usamos BitBlt( ) sobre la imagen usando la operacion SRCPAINT. El resultado es que las
reas que queremos que sean transparentes no cambian en el HDC destino mientras el resto
de la imagen es dibujada de forma usual.

SelectObject(hdcMem, g_hbmMask);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCAND);

SelectObject(hdcMem, g_hbmBall);
BitBlt(hdc, 0, bm.bmHeight, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0,
SRCPAINT);

Bastante simple eh? Afortunadamente lo es, pero resta una pregunta... de donde viene la
mscara? Hay bsicamente dos formas de obtener la mscara...

Hacerla uno mismo en un programa de edicon de imgenes. Esta es una decisin


rasonable si estamos usando una cantidad limitada de imgenes. De esta forma
podemos agregar el recurso mscara a nuestro programa y cargarlo usando
LoadBitmap( ).
Generarla cuando se ejecuta nuestro programa seleccionando un color en la imagen
original como el "color transparente" y crear una mscara que sea blanca donde
aparezca dicho color y negra en caso contrario.

Debido a que la primer opcin no es nada nuevo, deberas ser capaz de hacerlo si lo deseas.
La segunda forma proviene de aplicar algunos malabares usando BitBlt( ) y por lo tanto
voy a mostrar una forma de realizarla.

Como crear una Mscara

La forma ms simple de hacerlo es recorrer cada pixel en la imagen, chequear su valor y


luego poner en el correspondiente pixel de la mscara el color blanco o negro. Si usamos
BitBlt( ) (usando SRCCOPY) desde un HDC que referencia una imagen a color dentro de un
HDC que referencia una imagen blanco y negro, ste chequear que color est especificado
como color de fondo en la imagen a color y pondr todos aquellos pixeles en Blanco,
culaquier otro pixel que no sea del color del fondo terminar siendo Negro.

Esto funciona perfectamente, debido a que todos lo que necesitamos hacer es poner el color
de fondo con el color que queremos transparentar y hacemos BitBlt( ) desde nuestra
imagen a la mscara. Observa que esto solo funciona con una mscara monocromo
(blanco y negro)... esto es, bitmaps que solo tienen 1 bit per pixel para definir el color. Si
intentamos esto con una imagen a color que slo tiene pixeles blanco y negro, pero que el
tamao para definir el color por pixel es mayor que un bit, digamos 16 bits o 24 bits, esto
no funciona.

Receuerdas la primer condicin anterior para obtener mscaras exitosas? Esta deca que
nuestra imagen necesitaba ser negra donde queramos que fuera transparente. Debido a que
el bitmap que he usado en este ejemplo ya cumple esa condicin, no necesita ningn
tratamiento especial. Pero si deseas usar este cdigo en otra imagen que tiene un color
diferente para transparentar (rosa es una eleccin comn), entonces necesitamos tomar un
segundo paso, esto es, usar la mscara que acabamos de crear para alterar la imagen
original, donde todo lo que queremos transparentar es negro. No hay problema si otros
lugares de la imagen tambin son de color negro, debido a que esos lugares no son blancos
en la mscara y por lo tanto no sern tranparentes. Podemos realizar esto utilizando
BitBlt( ) desde la nueva mscara a la imagen original, usando la operacin SCRINVERT,
la cual fija todas la reas que son blancas en la mscara a negras en la imagen a color.

Esta es una parte de un proceso complejo, por lo tanto es bueno tener a mano una funcin
muy til que hace todo esto por nosotros, aqu est:

HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)


{
HDC hdcMem, hdcMem2;
HBITMAP hbmMask;
BITMAP bm;
//Crea una mscara monocromo (1 bit).

GetObject(hbmColour, sizeof(BITMAP), &bm);


hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);

//Obtiene algunos HDCs que son compatibles con la driver de video

hdcMem = CreateCompatibleDC(0);
hdcMem2 = CreateCompatibleDC(0);

SelectBitmap(hdcMem, hbmColour);
SelectBitmap(hdcMem2, hbmMask);

//Ponemos el color de fondo de la imagen a color con el color que


//queremos que sea transparente

SetBkColor(hdcMem, crTransparent);

//Copiamos los bits de la imagen a color a la mscara


//Blanco y Negro... todo lo que sea del color de fondo termina siendo
//blanco y todo lo dems termina siendo negro... Como queramos

BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0,


SRCCOPY);

//Tomamos la nueva mscara y la usamos para poner el color


transparente
//en dentro de la imagen a color original, de esta manera el efecto
//de transparencia funcionar bien.

BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0,


SRCINVERT);

// Liberamos.

DeleteDC(hdcMem);
DeleteDC(hdcMem2);

return hbmMask;
}

NOTA: Esta funcin llama a SelectObject( ) para seleccionar temporalmente el bitmap


a color que le pasamos dentro de un HDC. Un bitmap mo puede seleccionarse en mas de un
HDC a la vez, por lo tanto asegrate que el bitmap no est seleccionado por otro HDC
cuando llames a esta funcin, de ser as la funcin fallar.

Ahora que tenemos nuestra super funcin, podemos crear una mscara a partir de la imagen
original una vez que la hallamos cargado:

case WM_CREATE:
g_hbmBall = LoadBitmap(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDB_BALL));
if(g_hbmBall == NULL)
MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK |
MB_ICONEXCLAMATION);

g_hbmMask = CreateBitmapMask(g_hbmBall, RGB(0, 0, 0));


if(g_hbmMask == NULL)
MessageBox(hwnd, "Could not create mask!", "Error", MB_OK |
MB_ICONEXCLAMATION);
break;

El segundo parmetro es, por su puesto, el color de la imagen original que queremos que
sea transparente, en este caso es negro.

Como funciona todo esto?


... te estars preguntando. Seguramente tu experiencia con C y C++ significa que
comprendes operaciones binarias como OR, XOR, AND NOT etc... No voy a explicar este
proceso completamente, pero intentar mostrarte como lo he usado en este ejemplo. Si mi
expicacin no es lo suficientemente clara, puedes leer algo sobre operaciones binarias, que
seguramente te ayudar a entenderlo. Comprender esto no es determinante para usar esta
funcin y puedes ignorar como funciona confiando siempre en que lo har correctamente.

SRCAND

La operacin SCRAND o ROP (Raster Operation) para BitBlt( ), significa combinar los
bits usando AND. Esto es: solo los bits que valen 1 en la imagen fuente Y en la imagen
destino quedan el el resultado final. Usamos esto con nuestra mscara para poner en negro
todos los pixeles que tendrn eventualmente un color de la imagen original. La mscara
tiene el color negro (en binario, todos 0) donde queremos que halla color y blanco (todos 1
en binario) donde queremos que halla trasnsparencia. Cualquier valor combinado con 0,
usando AND, es 0 y por lo tanto todos los pixeles que son negros en la mscara son puesto
en 0 en el resultado final. Cualquier valor que es combinado con 1 usando AND permanece
inalterable, por lo tanto si vala 1 al comienzo, finaliza valiendo 1 y si vala 0 finaliza
valiendo 0... Por lo tanto todos los pixeles que eran blanco en nuestra mscara, no alteran su
valor despus de la llamada a BitBlt(). El resultado es el del la imagen de la esquina
derecha del ejemplo.

SCRPAINT

SCRPAINT usa la operacin OR, por lo tanto si al menos uno de los 2 bits vale 1, entonces el
resultado valdr 1. Usamos esto en la imagen a color. Cuando la parte negra (transparente)
de nuestra imagen a color es combinada con los datos en el desitno usando OR, el resultado
es que los datos permanecen inalterados, debido a que cualquier valor combinado con 0
usando la operacin OR permanece igual.

Sin embargo, el resto de la imagen a color no es negro, y el si destino tampoco es negro,


entonces tenemos una combinacin de los colores fuente y destino. El resultado puede
verse en la segunda bola de la segunda fila de la figura ejemplo. Esta es la razn por la cual
primero usamos la mscara para poner en negro los pixeles que queremos que tengan color,
ya que cuando usamos OR con la imagen a color, los pixeles coloreados no se mezclan con
lo que haya dentro de ellos.

SCRINVERT

Esta es la operacin XOR usada para poner en negro el color transparente en nuestra
imagen original (Si no ya no es negro). Combinando un pixel negro de la mscara con un
pixel que no sea del color de fondo en el destino lo deja inalterable, mientras que al
combinar un pixel blanco de la mscara (que generamos definiendo un color particular
como "fondo") con el color de un pixel del fondo del destino, esto hace que se cancele y se
ponga en negro.

Ejemplo 2
El cdigo ejemplo en el proyecto bmp_two que viene junto con esta seccin contiene el
cdigo para la imagen del ejemplo de esta seccin. Este consiste en primero dibujar la
mscara y la imagen a color exactamente como son usando SRCCOPY, luego usa en cada una
por separado las operaciones SRCAND y SRCPAINT repectivamente y finalmente las combina
para producir el resultado final.

El fondo en este ejemplo es gris para hacer mas obvia la transparencia ya que usar estas
operaciones sobre un fondo blanco o negro hace dificil darse cuenta si est funcionando o
no

Timers y Animaciones
Ejemplo: anim_one

Antes de comenzar...
Antes que veamos cosas animadas necesitamos crear una estructura para almacenar la
posicin de la esfera entre las actualizaciones. Esta estructura almacenar la posicin actual
y el tamao de la esfera, como as tambin los valores delta (cuanto queremos mover la
esfera en cada frame).

Una vez que hemos declarado el tipo de la estructura, tambin declaramos una instancia
global de la misma. Esto est bien, debido a que solo tenemos una esfera, si fueramos a
animar varias de ellas deberiamos probablemente usar un arreglo o una lista para
contenerlas de una forma mas conveniente.

Tambin hemos definido una constante BALL_MOVE_DELTA la cual indica cuan lejos
queremos que la esfera se desplace en cada actualizacin. La razn para almacenar deltas
en la estructura BALLINFO es que queremos mover la esfera hacia arriba o hacia abajo, hacia
izquierda o derecha, de forma independiente, BALL_MOVE_DELTA es slo un nombre
significativo que nos facilitar la tarea de cambiar el valor en el futuro.

Ahora necesitamos inicializar la estructura despus de que cargamos nuestros bitmaps:

BITMAP bm;
GetObject(g_hbmBall, sizeof(bm), &bm);

ZeroMemory(&g_ballInfo, sizeof(g_ballInfo));
g_ballInfo.width = bm.bmWidth;
g_ballInfo.height = bm.bmHeight;

g_ballInfo.dx = BALL_MOVE_DELTA;
g_ballInfo.dy = BALL_MOVE_DELTA;

La esfera comienza en la esquina superior izquierda, movindose hacia la derecha y


bajando de acuerdo a los miembros dx y dy de la estructura BALLINFO.

Configurando el Timer
La forma ms fcil de agregar un timer dentro de un programa en Windows es con
SetTimer(), aunque no es la mejor forma y tampoco es la forma recomendada para
aplicacines multimedia o juegos, sin embargo es lo suficientemente buena para
aplicaciones simples como sta. Cuando necesites algo mejor, chale una mirada a
timeSetEvent() en MSDN, la cual es mas apropiada.
const int ID_TIMER = 1;
ret = SetTimer(hwnd, ID_TIMER, 50, NULL);
if(ret == 0)
MessageBox(hwnd, "Could not SetTimer()!", "Error", MB_OK |
MB_ICONEXCLAMATION);

Aqu hemos declarado un ID para el timer para que podamos referirnos a ste
posteriormente (para eliminarlo) y pusimos el timer en el manejador del mensaje
WM_CREATE de nuestra ventana principal. Cada vez que transcurre un determinado tiempo,
el timer enva un mensaje WM_TIMER a la ventana y pasa como parmetro en wParam su ID.
Debido a que slo tenemos un timer no necesitamos el ID, pero es muy til si utilizamos
mas de uno.

Hemos configurado el timer para que enve una seal cada 50 milisegundos, lo cual resulta
en aproximadamente 20 cuadros por segundo. He dicho aproximadamente debido a que,
como dije, SetTimer( ) es un poco impresiso, pero este no es un cdigo crtico donde
milisegundos mas milisegundos menos causarn estragos, por lo tanto podemos usarlo.

Animacin en WM_TIMER
Ahora, cuando obtenemos el mensaje WM_TIMER queremos calcular la nueva posicin de la
esfera y dibujarla en la nueva posicin.
case WM_TIMER:
{
RECT rcClient;
HDC hdc = GetDC(hwnd);

GetClientRect(hwnd, &rcClient);

UpdateBall(&rcClient);
DrawBall(hdc, &rcClient);

ReleaseDC(hwnd, hdc);
}
break;

He puesto el cdigo para actualizar y dibujar la esfera en sus respectivas funciones. Esto es
una buena prctica y nos permite dibujar la esfera fuera de WM_TIMER o de WM_PAINT sin
necesidad de duplicar el cdigo. Observa que el mtodo que utilizamos para obtener el
HDC en cada caso es diferente , por lo tanto es mejor dejar este cdigo en los manejadores
de mensajes y pasar el resultado dentro de la funcin DrawBall( ).

void UpdateBall(RECT* prc)


{
g_ballInfo.x += g_ballInfo.dx;
g_ballInfo.y += g_ballInfo.dy;

if(g_ballInfo.x < 0)
{
g_ballInfo.x = 0;
g_ballInfo.dx = BALL_MOVE_DELTA;
}
else if(g_ballInfo.x + g_ballInfo.width > prc->right)
{
g_ballInfo.x = prc->right - g_ballInfo.width;
g_ballInfo.dx = -BALL_MOVE_DELTA;
}

if(g_ballInfo.y < 0)
{
g_ballInfo.y = 0;
g_ballInfo.dy = BALL_MOVE_DELTA;
}
else if(g_ballInfo.y + g_ballInfo.height > prc->bottom)
{
g_ballInfo.y = prc->bottom - g_ballInfo.height;
g_ballInfo.dy = -BALL_MOVE_DELTA;
}
}

Todo lo que esto hace es bsicamente matemtico, sumamos el valor delta a la posicin x
para mover la esfera. Si el valor pasa afuera del rea cliente lo ponemos nuevamente en el
rango y cambiamos el valor delta a la direccin opuesta para que la esfera "rebote" en los
bordes.

void DrawBall(HDC hdc, RECT* prc)


{
HDC hdcBuffer = CreateCompatibleDC(hdc);
HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc-
>bottom);
HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);

HDC hdcMem = CreateCompatibleDC(hdc);


HBITMAP hbmOld = SelectObject(hdcMem, g_hbmMask);

FillRect(hdcBuffer, prc, GetStockObject(WHITE_BRUSH));

BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width,


g_ballInfo.height, hdcMem, 0, 0, SRCAND);

SelectObject(hdcMem, g_hbmBall);
BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width,
g_ballInfo.height, hdcMem, 0, 0, SRCPAINT);

BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);

SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);

SelectObject(hdcBuffer, hbmOldBuffer);
DeleteDC(hdcBuffer);
DeleteObject(hbmBuffer);
}

Este es escencialmente el mismo cdigo de dibujo de los ejemplos anteriores, con la


excepcin de que obtiene la posicin y las dimensiones de la esfera a partir de la estructura
BALLINFO. Sin embargo hay una diferencia importante...

Doble Buffering
Cuando dibujamos directamente al HDC de la ventana, es totalmente posible que la pantalla
se actualice antes de que lo hagamos... por ejemplo, despus de que dibujemos la mscara y
antes que dibujemos la imagen original encima el usuario puede ver un parpadeo del fondo
antes de que nuestro programa tenga una chance de de dibujar sobre ste la imagen en
color. Mientras mas lerda sea la computadora y mientras mas operaciones de dibujo
realicemos, aparecern mas parpadeos.

Esto es terriblemente drstico y podemos resolverlo simplemente haciendo primero todos


los dibujos en memoria y luego copiar a la pantalla la obra maestra completa en un solo
BitBlt() para que la pantalla sea actualizada directamente de dicha imagen, sin que sean
visibles ninguna de las operaciones intermedias.

Para hacer esto creamos en memoria un HBITMAP temporal que es del tamao exacto del
rea que vamos a dibujar sobre la pantalla. Tambin necesitamos un HDC para que
podamos usar BitBlt() sobre el bitmap.

HDC hdcBuffer = CreateCompatibleDC(hdc);


HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc-
>bottom);
HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);

Ahora que tenemos un lugar en memoria donde dibujar, todas las operaciones de dibujo
usan hdcBuffer en vez de hdc (la ventana) y los resultados son almacenados sobre el
bitmap en memoria hasta que hallamos terminado. Luego podemos copiar dicha imagen a
la ventana en un solo "disparo".

BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);


Por ltimo podemos eliminar nuestro HDC y HBITMAP de manera usual.

Doble Buffering Veloz

En este ejemplo estoy creando y destruyendo el bitmap usado para el doble buffering con
cada frame, hice esto porque es ms fcil siempre crear un nuevo bffer que registrar
cuando cambia la posicin de la ventana y cambiar el tamao del bffer. Pero podra ser
ms eficiente crear un doble bffer global y no permitir cambiar el tamao de la ventana o
slo cambiar el tamao del bitmap cuando es cambiado el tamao de la ventana, en vez de
crearlo y destruirlo todo el tiempo. Se deja como tarea al lector implementar esta
optimizacin si desea mejorar el dibujado sobre la pantalla, para un juego o algo por el
estilo.

Eliminando el Timer
Es una buena idea liberar todos los recursos cuando la ventana es destruida y en este caso
tambin se incluye el timer que hemos usado. Para detenerlo, simplemente llamamos a
KillTimer( ) y le pasamos el ID que hemos usado para crearlo.

KillTimer(hwnd, ID_TIMER);

Texto y Fuentes
Ejemplo: font_one

Carga de Fuentes
La GDI Win32 tiene algunas capacidades admirables para trabajar con diferentes tipos de
estilos de presentacin, lenguajes, conjuntos de caracteres etc... Uno de los inconvenientes
de esto es que trabajar con fuentes luce algo intimidatorio para el que recin se inicia.
CreateFont(), la principal API para trabajar con fuentes tiene 14 parmetros, los cuales
especifican el alto, estilo, ancho, familia y otros varios atributos.
Afortunadamente no es tan difcil como parece y una gran parte del trabajo es realizada con
valores por default. Todos menos dos de los valores de CreateFont() pueden ser puestos
en 0 o en NULL para que el sistema use los valores por default dando como resultado una
fuente plana y comn.

CreateFont( ) crea un HFONT, un handle a la Fuente Lgica en memoria. Los datos


referenciados por este handle pueden ser recuperados dentro de una estrucura LOGFONT
usando la operacin GetObject( ), de la misma manera que lo hacamos con los bitmaps
donde la estructura BITMAP era rellenada a partir de un HBITMAP. Los miembros de LOGFONT
son idnticos a los parmetros de CreateFont() y por conveniencia podemos crear
directamente una fuente apartir una estructura LOGFONT ya existente, usando
CreateFontIndirect( ). Esto es muy til debido a que hace mas fcil la tarea de crear
una nueva fuente a partir de un handle cuando solo queremos alterar ciertos aspectos de
sta. Para rellenar los campos de la estructura LOGFONT usamos GetObject( ), alteramos
los campos que deseamos y creamos una nueva fuente usando CreateFontIndirect( ).

HFONT hf;
HDC hdc;
long lfHeight;

hdc = GetDC(NULL);
lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
ReleaseDC(NULL, hdc);

hf = CreateFont(lfHeight, 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, 0, 0,
"Times New Roman");

if(hf)
{
DeleteObject(g_hfFont);
g_hfFont = hf;
}
else
{
MessageBox(hwnd, "Font creation failed!", "Error", MB_OK |
MB_ICONEXCLAMATION);
}

Este es el cdigo usado para crear la fuente en la imagen ejemplo. La fuente es Times New
Roman de 12 puntos con el estilo Itlica El flag para obtener la fuente en itlica es el sexto
parmetro de CreateFont( ), el cual se puede apreciar que est puesto en TRUE. El ltimo
parmetro indica el nombre de la fuente que queremos usar.

La nica parte tramposa de este cdigo es el valor usado para el tamao de la fuente, el
parmetro lfHeight de CreateFont( ). Generalmente las personas, cuando trabajan con
fuentes, estn acostumbradas a trabajar con tamaos especificados en Puntos, Tamao 10,
Tamao 12, etc... Sin embargo, CreateFont( ) no acepta tamaos en puntos,solo acepta
Unidades Lgicas, las cuales son diferentes en nuestra pantalla que en nuestra impresora y
an entre impresoras y pantallas.
La razn de que exista esta situacin es porque la resolucin de los diferentes dispositivos
es bastante diferente... las impresoras pueden imprimir de 600 a 1200 pixeles por pulgada,
mientras que una pantalla con suerte llega a mostrar unos 200. Si usamos el mismo tamao
para la pantalla que para la impresora, quizs no podamos ver algunos caracteres en
particular.

Todo lo que tenemos que hacer es convertir el tamao punto al tamao lgico para un
dispositivo determinado. En este caso el dispositivo es la pantalla, por lo tanto obtenemos
el HDC de la pantalla y obtenemos el nmero de pixeles lgicos por pulgada usando
GetDeviceCaps() y metemos esto dentro de la frmula provista tan generosamene en
MSDN la cual usa MulDiv( ) para convertir nuestro tamao punto, en este caso 12, al
tamao lgico que CreateFont( ) espera. Luego almacenamos este en lfHeight y lo
pasamos como primer parmetro de CreateFont( ).

Fuentes por Default

Cuando llamamos por primera vez a GetDC( ) para obtener el HDC de nuestra ventana, la
fuente por default que es seleccionada es System, la cual para ser honesto, no es muy
atractiva. La forma ms simple de obtener una fuente mejor sin tener que pasar por
CreateFont() es llamar a GetStockObject() y preguntar por la DEFAULT_GUI_FONT.

Este es un objeto del sistema y podemos usarlo todas las veces que deseamos sin necesidad
retener memoria, podemos llamar a DeleteObject() para borrarlo pero esto no causar
efecto, lo cual es una buena idea porque no tendremos que averiguar si dicha fuente fu
creada con CreateFont( ) o con GetStockObject( ) antes de intentar eliminarla.

Mostrando Texto
Ahora que tenemos una "linda" fuente, como hacemos para mostrar algn texto en la
pantalla? Esto es, asumiendo que no queremos usar un control Edit o un control Static.

Las opciones bsicas son TextOut( ) y DrawText( ). TextOut() es mas simple pero
tiene menos opciones y no hace cubrimientos de palabras o alineacin por nosotros.

char szSize[100];
char szTitle[] = "These are the dimensions of your client area:";
HFONT hfOld = SelectObject(hdc, hf);

SetBkColor(hdc, g_rgbBackground);
SetTextColor(hdc, g_rgbText);

if(g_bOpaque)
{
SetBkMode(hdc, OPAQUE);
}
else
{
SetBkMode(hdc, TRANSPARENT);
}

DrawText(hdc, szTitle, -1, prc, DT_WORDBREAK);

wsprintf(szSize, "{%d, %d, %d, %d}", prc->left, prc->top, prc->right,


prc->bottom);
DrawText(hdc, szSize, -1, prc, DT_SINGLELINE | DT_CENTER |
DT_VCENTER);

SelectObject(hdc, hfOld);

La primer cosa que hacemos es usar SelectObject( ) para obtener la fuente que
queremos usar dentro de nuestro HDC y para prepararnos para dibujar. Todas las operaciones
de texto usarn, de aqu en mas, esta fuente hasta que se seleccione alguna otra.

Ahora vamos a definir los colores del texto y del fondo. Cuando fijamos el color de fondo
no cambiamos todo el fondo a un determinado color, solo afecta a ciertas operaciones que
dibujan texto usando el color de fondo. Esto tambin depende del Modo de Fondo (o
Background Mode) que est seleccionado actualmente. Si est definido como OPAQUE (el
default) entonces cada vez que se escriba texto, el color de fondo del texto ser el que est
seleccionado como background color. Si est definido como TRANSPARENT, el texto es
dibujado sin color de fondo y quedar como fondo del texto lo que sea que encuentre
dibujado debajo de ste.

Ahora realmente dibujamos el texto usando DrawText( ), pasamos el HDC a usar y el string
a dibujar. El tercer parmetro es la longitud del string, pero hemos pasado -1 debido a que
DrawText( ) es lo suficientemente astuto para darse cuenta cual es la longitud del texto.
En el cuarto parmetro pasamos prc, el puntero al cliente RECT. DrawText( ) dibujar
dentro de este rectngulo basndose en los otros flags que le pasamos.

En la primer llamada especificamos DT_WORDBREAK, el cual alinea por default el texto a la


izquierda y acomodar el texto automticamente a los lmites del rectngulo... muy til.

Para la segunda llamada solo imprimimos una sola lnea sin ajustar el texto al rectngulo y
la centramos horizontalmente y verticalmente (lo cual har DrawText() cuando dibujemos
una sola lnea).

Redibujar el Cliente

Slo una nota sobre el programa ejemplo... cuando WNDCLASS es registrada he puesto los
estilos CS_VREDRAW y CS_HREDRAW. Esto provoca que el rea cliente entera sea sea
redibujada si la ventana cambia su tamao, mientras que por default solo se dibujan las
partes que han cambiado. Esto luce algo malo debido a que el texto centrado es movido
cuando la ventana cambia su tamao pero no se actualiza como esperamos.

Eleccin de Fuentes
En general cualquier programa que utiliza fuentes le permite al usuario escoger su propia
fuente, como tambin el color y los atributos de estilo a usar.

Al igual que los dilogos comunes para abrir y guardar archivos, hay un dilogo comn
para escoger una fuente. Este es llamado ChooseFont( ) y funciona junto con la estructura
CHOOSEFONT.

HFONT g_hfFont = GetStockObject(DEFAULT_GUI_FONT);


COLORREF g_rgbText = RGB(0, 0, 0);
void DoSelectFont(HWND hwnd)
{
CHOOSEFONT cf = {sizeof(CHOOSEFONT)};
LOGFONT lf;

GetObject(g_hfFont, sizeof(LOGFONT), &lf);

cf.Flags = CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;


cf.hwndOwner = hwnd;
cf.lpLogFont = &lf;
cf.rgbColors = g_rgbText;

if(ChooseFont(&cf))
{
HFONT hf = CreateFontIndirect(&lf);
if(hf)
{
g_hfFont = hf;
}
else
{
MessageBox(hwnd, "Font creation failed!", "Error", MB_OK |
MB_ICONEXCLAMATION);
}

g_rgbText = cf.rgbColors;
}
}

El hwnd en esta llamada es simplemente la ventana que vamos a usar como padre del
dilogo.

La forma ms fcil de usar este dilogo es junto con una estructura LOGFONT, la cual es
bastante parecida a la estructura HFONT que venamos usando. Hacemos que el campo
lpLogFont apunte a LOGFONT, que acabamos de llenar con informacin y tambin
agregamos el flag CF_INITTOLOGFONTSTRUCT para que ChooseFont( ) use este campo. El
flag CF_EFFECTS le dice a ChooseFont( ) que permita al usuario seleccionar un color
como as tambin efectos de subrayado y tachado.

Los estilos Negrita e Itlica no se cuentan como efectos, son considerados partes de la
fuente y de hecho algunas fuentes solo vienen en Negrita o en Itlica. Si queremos chequear
o prevenir al usuario de seleccionar una fuente negrita o itlica, podemos chequear los
campos lfWeight y lfItalic, respectivamente, de la estructura LOGFONT despus de que
el usuario ha hecho su eleccin. Luego se le puede preguntar al usuario si desea hacer otra
seleccin o algn cambio a los campos antes de llamar a CreateFontIndirect().

El color de una fuente no est asociado con un HFONT y por lo tanto debe ser almacenado
por separado. EL campo rgbColors de la estructura CHOOSEFONT es usado para pasar el
color inicial y para recuperar luego el nuevo color.

CF_SCREENFONTS indica que queremos fuentes diseadas para funcionar en la pantalla y no


fuentes diseadas para funcionar en una impresora. Algunos soportan ambas, otros soportan
una u otra. Dependiendo de para que vas a usar la fuente, este y muchos otros flags pueden
ser encontrados en MSDN para definir exactamente que fuentes deseamos que el usuario
sea capaz de seleccionar.

Eleccin de colores
Para permitirle al usuario cambiar el color de la fuente, existe el dilogo comn
ChooseColor( ). Este es el cdigo usado para permitirle al usuario seleccionar el color de
fondo en programa ejemplo.
COLORREF g_rgbBackground = RGB(255, 255, 255);
COLORREF g_rgbCustom[16] = {0};
void DoSelectColour(HWND hwnd)
{
CHOOSECOLOR cc = {sizeof(CHOOSECOLOR)};

cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR;


cc.hwndOwner = hwnd;
cc.rgbResult = g_rgbBackground;
cc.lpCustColors = g_rgbCustom;

if(ChooseColor(&cc))
{
g_rgbBackground = cc.rgbResult;
}
}

Esto es algo complicado, nuevamente estamos usando el parmetro hwnd como padre del
dilogo. El parmetro CC_RGBINIT indica que debemos comenzar con el color que pasamos
en el campo rgbResult, el cual tambin es donde obtenemos el color que el usuario ha
seleccionado cuando el dilogo se cierra.

El arreglo g_rgbCustom de 16 COLORREFs es necesario para almacenar cualquier valor que


el usuario decide poner dentro de la tabla de colores personalizados includa en el dilogo.
Podemos potencialmente almacenar estos valores en otro lugar que no sea el registro,
porque de no ser as dichos valores se perdern al cerrar el programa. Este parmetro no es
opcional.

Cambio de la Fuente en los Controles


Algo que podemos hacer en algn punto es cambiar la fuente de los controles en los
dilogos o ventanas. Generalmente este es el caso cuando usamos CreateWindow( ) para
crear controles como lo hemos hecho en los ejemplos anteriores. Los controles al igual que
las ventanas usan la fuente System por default, por lo tanto usamos WM_SETFONT para
definir un nuevo handle a la fuente (de GetStockObject()) para que use el control.
Podemos usar este mtodo con fuentes que creamos usando CreateFont(). Simplemente
pasamos el handle a la fuente como wParam y ponemos lParam en TRUE para hacer que el
control se redibuje.

He realizado esto en los ejemplos anteriores, pero tiene sentido mencionarlo aqu debido a
que es relevante y muy breve:

SendDlgItemMessage(hwnd, IDC_OF_YOUR_CONTROL, WM_SETFONT,


(WPARAM)hfFont, TRUE);

Donde hfFont es, por su puesto, el HFONT que queremos usar y IDC_OF_YOUR_CONTROL es
el ID del control al cual le queremos cambiar la fuente.

Libros Recomendados y Referencias


Libros
Si esperas que alquien online te trate con respeto mientras ests aprendiendo, entonces
NECESITAS un buen libro de donde aprender. Estamos aqu para dirigir y explicar
aquellas cosas que necesitan explicacin, no para ensearte paso por paso.

Puedes encontrar mas libros recomendados y links donde comprar en #Winprog Store.

Programming Windows
by Charles Petzold. El libro para empezar con Win32 API. Si quieres escribir
programas usando solo la API (la cual cubre este tutorial), entonces necesitas este
libro.
Programming Windows with MFC
by Jeff Prosise. Si quieres aventurarte en la MFC (DESPUES de aprender a usar la
API Win32), este libro es para vos. Si no te gusta la MFC pero quieres buscar
trabajo desarrollando aplicaciones en Windows, entoces consigue este libro de todos
modos.
Programming Applications for Windows
by Jeffrey Richter. No es para principientes, cubre el manejo de procesos y
threads, dlls, manejo de memoria, manejo de excepciones y otros.
Visual C++ Windows Shell Programming
by Dino Esposito. Para aquellos que estn interesados en los aspectos visuales e
interfaces amigables en Windows, este libro cubre extensiones de la interfaz de
Windows, cmo trabajar eficientemente con archivos y drag and drop (arrastrar y
soltar con el mouse), cmo personalizar la barra de tareas y el Explorador de
Windows y otra gran cantidad de aspectos. Ideal para aquellos que desean escribir
aplicaciones con Interfaces Graficas al Usuario en Windows.
Network Programming for Microsoft Windows
Informacin actualizada de programacin en redes, que incluye NetBIOS, mailslots
y pipes y por su puesto, los siempre importantes, Windows sockets incluyendo
winsock2 y raw sockets. Tambin contiene informacin especfica sobre las
distintas plataformas incluyendo 2000 y CE.

Links
MSDN Online
Este sitio tiene referencias a todas las tecnologas imaginables de Microsoft,
incluyendo Win32 API y documentacin MFC. Si no es includa con tu compilador
(VC++) puedes conseguir esta informacin de manera gratuita.
#winprog homepage
Mira en FAQ y Store

Herramientas de Lnea de Commando


para VC++
Cmo Obtenerlas
Microsoft ha lanzado completamente las herramientas de su compilador de lnea de
comando y del enlazador como parte de .Net Framework SDK. El Framework SDK viene
con todo lo necesario para el desarrollo .NET (compilador C#, etc...), incluyendo el
compilador de lnea de comando cl.exe, el cual si bien est destinado a ser usado con
.NET Framework, es el mismo compilador que viene con Visual C++ Standard.

.NET Framework SDK

Debido a que este es el .Net SDK, no viene con los headers (*.h) y las libreras necesarias
para el desarrollo Win32 API, ya que estos son partes de la plataforma SDK. De todas
maneras, la plataforma SDK es gratuita, solo necesitas bajar el Core SDK y si deseas
puedes descargar algunos otros componentes.

Platform SDK

Como bonus, si descargas la documentacin de la plataforma SDK (lo cual es altamente


recomendado), tendrs una referencia completa y actualizada sobre Win32 la cual es
MUCHO mas fcil de usar que MSDN online.
Recuerda chequear las opciones de Register Enviroment Variables en ambos SDKs, de
no ser as, necesitars chequear la ruta y otras variables antes de que las herramientas
funcionen en la lnea de comando.

Cmo Usarlas
Debido a que se provee una comprensiva documentacin, tambin accesible va MSDN
online necesitars estudiarla un poco para aprender acerca de los compiladores VC++ y sus
herramientas. Para comenzar aqu estn las formas bsicas de crear un programa...

Para hacer una simple aplicacin de consola:

cl foo.c

Y para crear un aplicacin simple en Windows como los ejemplo de ste tutorial:

rc dlg_one.rc
cl dlg_one.c dlg_one.res user32.lib

Herramientas de Lnea de Comando para


Borland C++
Cmo Obtenerlas
Afortunadamente para aquellos que quieren introducirse en el desarrollo en Windows,
Borland ha ofrecido sus herramientas de lnea de comando para el pblico en general de
forma gratuta. Que carcteristicas tienen estos compiladores? Bueno, no incluyen un
hermoso IDE (Entorno Integrado de Desarrollo) o un editor de recursos, pero debo decir
que el compilador en s es mejor (lejos) que LCC-Win32 (el cual an no inluye C++) o la
gran cantidad que andan dando vueltas como gcc, mingw, cygwin, djgpp, etc...

Lee el archivo Readme para obtener ayuda de cmo configurarlo.

Borland C++ 5.5 Lo bueno de este compilador es que viene con un debugger! No lo he
usado, por lo tanto no puedo ofrecerte mucha ayuda sobre l, pero es mejor que nada. Si has
usado Turbo C++ en la poca del DOS, puedo decirte que es algo parecido.

Por alguna razn Internet Explorer parece tener algn problema descargando este archivo,
ya que hacer click sobre el link no funciona. Lo mejor es hacer click con el botn derecho y
luego hacer click en copiar vnculo, por ltimo usa tu cliente FTP favorito para descargarlo.

Turbo Debugger
Por ltimo, pero no menos importante, un archivo de ayuda de Windows con una
Referencia Completa de Win32 API. Tiene algunos aos, pero todava es apropiada y es
mucho mas conveniente que MSDN online, a menos que necesites acceder a los agregados
mas recientes en la API. Yo los uso regularmente.

Win32 API Reference

Cmo usarlas
Comandos Bsicos

Si quieres compilar un programa de un solo archivo (por ejemplo simple_window.c),


entonces puedes usar el siguiente comando:
bcc32 -tW simple_window.c

La opcin -tW especifica una aplicain GUI Win32, en vez de la aplicacin en consola que
se utiliza por default. Puedes compilar mltiples archivos dentro de un solo archivo .exe
agregando los otros archivos al final de dicho comando.

Enlazando los Recursos

Este es un aspecto muy frustante para muchos usuarios de lnea de comando, parece ser que
borland intenta hacerlo lo mas difcil posible. El compilador de recursos brc32 no se
comporta como lo haca en las primeras versiones donde poda enlazar los recursos
compilados dentro de el archivo .exe. Cuando ejecutas brc32 sin opciones para obtener la
ayuda, todava muestra una opcin para poner el enlazado .exe enOFF, pero parece no
haber una forma de ponerlo en ON.

He intentado varias combinaciones de comandos y opciones pero no encuentro la forma de


agregar un archivo .res a un .exe creado con el mtodo anterior, lo que es bastante molesto
debido a que la forma que he encotrado para hacerlo es bastante mas complicada.

Sin embargo hay una forma fcil...

BC++ ahora tienen un mtodo alternativo para incluir recursos en un programa mediante el
uso de un #pragma (una directiva no-estndar del preprocesador que el compilador ignorar
si no la reconoce).

#pragma resource "app_name.res"

Al poner este cdigo dentro de tu archivo principal .c o .cpp, el compilador


automticamente enlazar el archivo .res que es generado a partir de archivo de recursos .rc
(.res es como un .obj)

Usar la directiva #pragma te permitir compilar programas de una forma simple como la
anterior, pero recuerda que antes necesitas compilar el archivo .rc usando brc32.
La forma difcil

Estos son los comando que hay que usar para compilar el ejemplo dlg_one, incluyendo el
archivo de recursos.

bcc32 -c -tW dlg_one.c


ilink32 -aa -c -x -Gn dlg_one.obj c0w32.obj,dlg_one.exe,,import32.lib
cw32.lib,,dlg_one.res

La opcin -c de bcc32 significa "solo compilar", no enlazar dentro de un .exe. Las opciones
-x -Gn libera algunos archivos extra que genera el enlazador, que probablemente no
necesites.

Para hacer las cosas mas fcil, lo mejor es hacer todo esto en un archivo makefile. He
preparado uno genrico que debera funcionar con todos los ejemplos de este tutorial y
deberas ser capaz de adaptarlo a cualquiera de tus programas.

APP = dlg_one
EXEFILE = $(APP).exe
OBJFILES = $(APP).obj
RESFILES = $(APP).res
LIBFILES =
DEFFILE =

.AUTODEPEND
BCC32 = bcc32
ILINK32 = ilink32
BRC32 = brc32

CFLAGS = -c -tWM- -w -w-par -w-inl -W -a1 -Od


LFLAGS = -aa -V4.0 -c -x -Gn
RFLAGS = -X -R
STDOBJS = c0w32.obj
STDLIBS = import32.lib cw32.lib

$(EXEFILE) : $(OBJFILES) $(RESFILES)


$(ILINK32) $(LFLAGS) $(OBJFILES) $(STDOBJS), $(EXEFILE), , \
$(LIBFILES) $(STDLIBS), $(DEFFILE), $(RESFILES)

clean:
del *.obj *.res *.tds *.map

Slo necesitas modificar las primeras 6 lneas con la informacin apropiada.

Soluciones a Errores Comunes


Error LNK2001: unresolved external symbol _main
Error C2440: cannot convert from 'void*' to 'HICON__ *' (o similar)
Fatal error RC1015: cannot open include file 'afxres.h'
Error LNK2001: unresolved external symbol InitCommonControls
El Dilogo no es mostrado cuando se agregan ciertos controles

Error LNK2001: unresolved external symbol _main


Un "unresolved external" (llamada externa no resuelta) ocurre cuando algn cdigo tiene
una llamada a una funcin en otro mdulo y el enlazador no puede encontrar dicha funcin
en ninguno de los mdulos o libreras que ests enlazando.

En este caso especfico, esto significa una de dos cosas: Ests intentando escribir una
aplicacin Win32 GUI (o una aplicacin no-consola) y accidentalmente la ests
compilando como Console aplication... realmente ests escribiendo y compilando una
aplicacin de consola pero no escribiste o no compilaste apropiadamente una funcin
main().

Generalmente el primero es el error ms comn, si especificas como tipo de proyecto en


VC++, Win32 Console, cuando crees tu proyecto obtendrs este error. Tambin puedes
obtener este error si intentas compilar desde la linea de comandos usando BC++ y olvidas
especificar los parmetros correctos para decirle al compilador que haga una aplicacin
Win32 GUI en lugar de una aplicacin de consola, la cual es la especificada por default.

Correccin

Si ests usando VC++, debes re-crear el proyecto y seleccionar como tipo de proyecto
Win32 Aplication (NO "Console").

Si ests el compilador de lnea de comando, BC++, debes usar -tW para especificar que se
trata de una aplicacin Windows.

Error C2440: cannot convert from 'void*' to 'HICON__


*' (o similar)
Si ests compilando el cdigo de este tutorial, eso significa que lo ests compilando como
cdigo C++. El cdigo est escrito para los compiladores de C bcc32 y VC++ y dicho
cdigo no puede compilarse exactamente igual bajo C++ debido a que C++ tiene muchas
reglas estrictas acerca de conversin de tipos. C simplemente lo dejar pasar, C++ requiere
que lo explicites.

VC++ (y la mayora de los compiladores) automticamente compilan un archivo con la


extensin .cpp cuando se trata de cdigo C++ y con la extensin .c cuando se trata de
cdigo C. Si has agregado el cdigo del tutorial a un archivo .cpp, est es la razn exacta
para obtener este error.

Si ests compilando cdigo que no sea de este tutorial, no puedo garantizarte que sea
correcto y por lo tanto puede ser un error que proviene de otro lado. Debes determinar si es
seguro convertir el valor para remover el error o si realmente ests intentando que una
variable sea algo que no es.

Correcin

Si quieres usar C, simplemente renombra el archivo .cpp a uno con extensin .c. En otro
caso, debes agregar un cast, todo el codigo de este tutorial funciona sin ningn tipo de
cambios si se lo compila como C++.

Por ejemplo, esto funcionara en C:

HBITMAP hbmOldBuffer = SelectObject(hdcBuffer, hbmBuffer);

Pero en C++ necesita un cast

HBITMAP hbmOldBuffer = (HBITMAP)SelectObject(hdcBuffer,


hbmBuffer);

Fatal error RC1015: cannot open include file 'afxres.h'.


VC++ agrega afxres.h a los archivos de recursos an cuando no estamos usando un
proyecto MFC y este archivo slo puede ser instalado si se instala MFC. Este particular
archivo realmente no es necesario, por lo tanto para corregir el error podemos editar el
archivo .rc en el block de notas y reemplazar ambas apariciones de afxres.h con
winres.h (observa que debe haber dos apariciones y debes cambiar ambas).

Error LNK2001: unresolved external symbol


InitCommonControls
No ests enlazando la librera comctl32.lib, en la cual est definida esta API. Esta librera
no es includa por default, por lo tanto necesitars agregarla a las libreras en la lnea de
comando o agregarla en las especificacines de tu proyecto en VC++, en la parte de Link.

El Dilogo no es mostrado cuando se agregan ciertos


controles
Los controles como ListView, TreeView, Hotkey, Progress Bar, entre otros, son
clasificados como Controles Comunes (Common Controls) y son inlcudos en la librera
commctl32.dll y no estn disponibles para versiones anterires de Windows 95. Los
controles como BUTTON, EDIT, LISTBOX, etc... si bien parecen ser comunes, NO son
"Controles Comunes" por lo que generalmente nos referimos a ellos como "Controles
Estandar".
Si agregas un Common Control a un dilogo y ste falla al intentar mostrarlo, seguramente
ha falle la llamada antes que ejecutes el dilogo . El mejor lugar para realizar esta llamada
es en WinMain(). Si realizamos la llamada en WM_INITDIALOG, es demasiado tarde debido a
que el dilogo fallar antes de que llegue a este punto y por lo tanto nunca ser llamado.

Algunas personas y algunas documentaciones pueden decirte que InitCommonControls()


est obsoleto y que debes usar InitCommonControlsEX(), pero eres libre de elegir,
InitCommonControls() es mas simple y no hay nada malo en usarla.

Porqu deberas aprender la API antes que


la MFC
La Controversia
Muchas personas entran al IRC y preguntan "Que es mejor, MFC o API?" y muchas otras
personas estn dipuestas a responder "MFC es un m..." o "API es una m...", ya sea porque
quizs una o lo otra estubo involucrada en algn evento traumtico en su infancia, o
simplemente porque todos dicen que una es mejor que la otra.

Los argumentos estndar son:

API es muy difcil


MFC es demasiado confusa
API es mucho cdigo
MFC est mal diseada
API no es orientada a objetos
MFC pate a mi perro
API se rob a mi novia

Y as...

Mi Respuesta
Primero de todo vamos a aclarar que son la API y la MFC. API es un trmino genrico que
significa Aplication Programming Interface (Interface para la Programacin de
Aplicaciones). Sin embargo, en el contexto de la programacin en Windows, esto se refiere
especficamente a la API de Windows, la cual es el nivel mas bajo de interaccin entre las
aplicaciones y el sistema operativo. Los drivers, por su puesto, tienen an niveles inferiores
y trabajan con diferentes conjuntos de llamadas a funciones, pero para la gran majora de
desarrollo en Windows esto no es relevante. MFC es una Librera de Clases, un conjunto
de clases C++ que han sido escritas para reducir la cantidad de trabajo que tomara realizar
ciertas cosas utilizando la API. La MFC adems introduce dentro de la aplicacin un
esqueleto (o framework) Orientado a Objetos, de cual puedes tomar ventaja o ignorarlo si
deseas, como lo hacen la mayora de los principiantes debido a que este framework no est
apuntado a escribir reproductores de mp3, clientes IRC o juegos.

Entonces, Cul es el framework correcto?... Para principiantes, aquellas personas que


simplemente estn aprendiendo a programar, creo que deberan trabajar con la API hasta
que estn a gusto con la manera en que funcionan las aplicaciones y comprendan todos los
mecanismos bsicos que hay detrs de cosas como el Loop de Mensajes, GDI, Controles y
quizs an mutlithreading y sockets. De esta forma comprendern los cimientos de todas
las aplicaciones en Windows y podrn aplicar este conocimiento comn a MFC, Visual
Basic o cualquier otro framework que no sporte todo lo que la API hace, simplemente
porque dicho framework ya hace una gran cantidad de cosas y no puede soportar todas esas
pequeas cosas que nadie usa. Entonces, cuando necesites usar esas cosas, es ah dinde
puedes agregarlas tu mismo utilizando tus conocimiento sobre la API.

Pero la MFC no es mas fcil? En cierta forma, es mas fcil cuando muchas tareas comunes
son realizadas por uno mismo, reduciendo la cantidad de cdigo que realmente necesitamos
tipear. Sin embargo, menos cdigo no significa "mas fcil" cuando no entendemos el
cdigo que necesitamos escribir, o cuando no entendemos cmo funciona dicho cdigo.
Generalmente los principiantes que utilizan los wizards para comenzar sus aplicaciones, no
tienen idea que hace la mayora del cdigo que se ha generado y pierden una gran parte del
tiempo intentando darse cuenta donde agregar cosas o que cambios deben hacer para para
alcanzar un cierto resultado. Si comienzas a escribir tus programas desde cero, ya sea
utilizando la API o MFC, entonces comprenders todo lo que est puesto all ya que fu
puesto por vos mismo y solo usars las cosas que entiendes como funcionan.

Otro factor importante es que la mayora de las personas que estn aprendiendo por primera
vez la API Win32, no tienen una fuerte base en C++. Intentar comprender la programacin
en Windows utilizando la MFC y aprender C++ al mismo tiempo puede ser una tarea
monumental. Si bien no es imposible, estar lejos de ser mas productivo que hacerlo
teniendo conocimientos sobre C++ y la API.

Entonces, bsicamente...
Lo que pienso es que deberas aprender la API hasta que te sientas cmodo con ella y luego
intentar con la MFC. Si luego, pareciera ser que tiene sentido y puedes ahorrar tiempo con
la MFC, entonces sala.

Sin embargo, y esto es importante... si quieres trabajar con la MFC sin entender la API y
luego buscas ayuda por algo y la respuesta que obtienes hace referencia a la API (como por
ejemplo "Usa el HDC provisto en el mensaje WM_CTLCOLORSTATIC"), seguramente
quedes colgado ya que no entiendes como traducir un objeto de la API a uno de la MFC. En
ese momento estars en problemas y las personas se frustrarn contigo por no aprender todo
aquello que necesitas de la API antes de intentar usar la MFC.

Personalmente prefiero trabajar con la API, solo me parece mejor. Pero si tengo que
escribir una terminal para una base de datos o un host para un conjunto de controles
ActiveX, considerara seriamente usar la MFC ya que podra eliminarme una gran cantidad
de cdigo que, en otro caso, debera reinventar.

Notas sobre Archivos de Recursos


Argh!
La nica cosa que realmente odiaba cuando cambi mi entorno de programacin, de
Borland C++ a MS VC++, fu la forma en que VC++ maneja los scripts de recursos
(archivos.rc).

En Borland C++ era libre de controlar la disposicin y el contenido de los archivos .rc y
cuando usaba el editor de recursos, slo las cosas que yo especficamente cambiaba eran
cambiadas en el archivo. En cambio, el editor de recursos de VC++, reescribe
completamente el archivo .rc y posiblemente destruye o ignora los cambios que
personalmente realizo.

En un principio esto fu terriblemente frustrante, pero bsicamente he aprendido a trabajar


con l y despus de todo no es TAN malo, debido a que, en general no escribo tanta
cantidad de recursos a mano, salvo aquellos con cambios menores que quizs no pueden
realizarse por completo en el editor de recursos.

Compatibilidad
Un pequeo cambio que se hizo en este tutorial fu lograr que los archivos de recursos
compilen de forma correcta bajo VC++ y BC++ sin realizar cambios. En el tutorial original
he usado la convencin de nombres para los encabezados de recursos utilizada por Borland,
la cual era nombre_proyecto.rh. Sin embargo, por default, en VC++ este encabezado
SIEMPRE es llamado resource.h, por lo tanto por simplicidad he adoptado este ltimo
para la revisin actual del tutorial que no impacta en BC++ (no del todo).

Para los curiosos, es posible cambiar el nombre del recurso que usa VC++ editando
manualmente el archivo .rc y cambiando el nombre en dos lugares, primero donde es
inlcudo (#include) y segundo en el recurso TEXTINCLUDE donde est contenido.

El segundo problema es que, por default, VC++ requiere que sea includo dentro del
archivo de recursos el archivo afxres.h , mientras que BC++ tiene todas las macros del
preprocesador que se necesitan, definidas de forma automtica y por lo tanto no requiere
dicha inclusin. Otra cosa asombrosa es que afxres.h solo es instalado si instalas MFC, lo
cual no todo el mundo hace, an cuando ests creando una aplicacin API la cul solo
requiere winres.h, que siempre es instalada.

Debido a que he trabajado con VC++ y he usado su editor de recursos, pude resolver este
problema alterando ligeramente cada archivo .rc incluyendo lo siguiente:
#ifndef __BORLANDC__
#include "winres.h"
#endif

Lo cual bajo circunstancias normales prodra leerse:

#include "afxres.h"
Para aquellos que estn usando VC++ pueden encontrar la opcin de cambiar este texto
dentro del IDE atravs de "View > Resource Includes". Generalmente, en la prctica
normal, no hay necesidad de hacer siempre esto, slo es una forma que he usado para
solucionar el problema de trabajar con VC++ y BC++ al mismo tiempo.

Para aquellos que usan BC++, pido disculpas por toda la basura que es generada por el
editor de recursos de VC++, pero no puedo hacer nada al respecto.

Compilacin de Recursos bajo BC++


Por mas que lo intente no puedo encontrar una forma simple de compilar un programa en
BC++ que incluya archivos RC y ltimamente tuve que configurarlo en una configuracin
no-ptima que encontrars en los archivos "make" includos con el cdigo fuente de este
tutorial. Puedes encontrar notas sobre el compilador de BC++ en Free Borland C++
Command Line Tools.

Anda mungkin juga menyukai