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...
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.
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.
#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.
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.
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.
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.
nuestro switch(). Ahora queremos que algo suceda cuando llegamos a esta nueva
parte de nuestro programa.
Primero presentar el cdigo que queremos agregar (el que le mostrar al usuario el
nombre del archivo del programa) y luego lo integrar dentro del nuestro programa.
Mas adelante, probablemente solo te muestre el cdigo y te dejar que lo integres tu
mismo. Esto es, por su puesto, mejor para mi porque no tengo que andar tipeando
demasiado y mejor para vos porque sers capaz de agregar el cdigo dentro de
CUALQUIER programa, no solamente el que estamos presentando. De todas
maneras, si no ests seguro de como hacerlo puedes mirar el cdigo fuente
correspondiente a esta seccin includo en el archivo zip.
GetModuleFileName(hInstance, szFileName, MAX_PATH);
MessageBox(hwnd, szFileName, "This program is:", MB_OK |
MB_ICONINFORMATION);
Este cdigo no puede ser copiado en cualquier lugar dentro de nuestro programa.
Especificamente queremos que ste se ejecute cuando el usuario clickea el mouse, por
lo tanto veremos como poner dicho cdigo dentro de nuestro programa:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN:
// Comienzo del nuevo cdigo
{
char szFileName[MAX_PATH];
HINSTANCE hInstance = GetModuleHandle(NULL);
GetModuleFileName(hInstance, szFileName, MAX_PATH);
MessageBox(hwnd, szFileName, "This program is:", MB_OK |
MB_ICONINFORMATION);
}
// Fin del nuevo cdigo
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Observa el nuevo conjunto de llaves { }. Estas son necesarias cuando declaramos una
variable dentro de una sentencia switch( ). Esto es conocimiento bsico de C pero
pienso que es bueno recordarlo para aquellos que estn haciendo las cosas por el
camino difcil.
Una vez que has agregado el cdigo, complalo. Si funciona, has click dentro de la
ventana y vers desplegado un cuadro con el nombre del archivo .exe .
Habrs notado que hemos agregado dos variables, hInstance y szFileName. Observa
la funcin GetModuleFileName y vers que el primer parmetro es
un HINSTANCE refirindose al mdulo ejecutable (nuestro programa,el archivo .exe).
Donde obtenemos eso? GetModuleHandle( ) es la respuesta. Las referencias
de GetModuleHandle( ) indican que si pasamos NULL como parmetro, nos retornar
un "Handle al archivo usado para crear el proceso de llamada" que es exactamente lo
que necesitamos: el HINSTANCE anteriormente mencionado. Poniendo todo esto,
finalizamos con la siguiente declaracin:
HINSTANCE hInstance = GetModuleHandle(NULL);
Ahora, con el segundo parmetro, nuevamente usamos nuestro confiable manual de
referencia. Este dice: "un puntero al buffer que recibe la ruta y el nombre de archivo
del modulo especificado" y el tipo de dato es LPTSTR (o LPSTR si tu referencia es vieja).
Debido a que LPSTR es equivalente a char* podemos declarar un arreglo de caracteres
como el siguiente:
char szFileName[MAX_PATH];
MAX_PATH es una macro includa va <windows.h> definida como la mxima longitud de
un buffer necesario para almacenar el nombre de un archivo bajo Win32.
Pasamos MAX_PATH a la funcinGetModuleFileName( ) para que sta conozca el
tamao del buffer.
Luego de que GetModuleFileName( ) es llamado, el bffer szFileName ser llenado
con un string terminado en cero conteniendo el nombre de nuestro archivo exe.
Pasamos este valor aMessageBox() porque es una forma fcil de mostrarlo en pantalla.
Por lo tanto, si has agregado esto en el cdigo, complalo. Si funciona haz click en la
ventana y debers ver desplegado un mensaje con el nombre del archivo.
Si esto no funciona, aqu esta el cdigo completo. Compralo con el tuyo para
encontrar que errores has cometido.
#include <windows.h>
const char g_szClassName[] = "myWindowClass";
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM
lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN:
{
char szFileName[MAX_PATH];
HINSTANCE hInstance = GetModuleHandle(NULL);
=
=
=
=
=
=
=
=
=
=
=
=
sizeof(WNDCLASSEX);
0;
WndProc;
0;
0;
hInstance;
LoadIcon(NULL, IDI_APPLICATION);
LoadCursor(NULL, IDC_ARROW);
(HBRUSH)(COLOR_WINDOW+1);
NULL;
g_szClassName;
LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
NULL, NULL, hInstance, NULL);
if(hwnd == NULL)
{
Que es un Mensaje?
Un mensaje es un valor entero. Si observas en tus archivos de cabecera (lo cual es una
buena prctica cuando investigamos el funcionamiento de las API`s) puedes encontrar
cosas como esta:
#define WM_INITDIALOG
#define WM_COMMAND
0x0110
0x0111
#define WM_LBUTTONDOWN
0x0201
...y asi siguiendo. Los mensajes son usados para comunicar la mayora de las cosas en
windows, al menos en los niveles bsicos. Si quieres que una ventana o control (el cual
es una ventana especializada) haga algo, debes enviarle un mensaje. Si otra ventana
quiere que vos hagas algo, entoces te enva un mensaje. Si ocurre un evento, como
cuando el usuario mueve el mouse, presiona el teclado, etc... entonces el sistema la
enva un mensaje a la ventana afectada. Dicha ventana recibe el mensaje y acta
adecuadamente.
Cada mensaje en windows puede tener hasta dos parmetros, wparam y lparam.
Originalmente wparam tena 16 bits y lparam tena 32 bits, pero en Win32 ambos son
de 32 bits. No todos los mensajes usan estos parmetros y cada mensaje los usa de
manera diferente. Por ejemplo, el mensaje WM_CLOSE no usa ninguno de los dos y por
lo tanto deberias ignorarlos. El mensajeWM_COMMAND usa ambos, wparam contiene dos
valores HIWORD(wparam) es la notificacin del mensaje (si se aplica)
y LOWORD(wparam) es el ID del control o men que envi el mensaje. lparam es
elHWND (Windows Handle) del control que envi el mensaje o nulo (NULL) si el mensaje
no proviene de un control.
HIWORD( ) y LOWORD( ) son macros definidas por windows que simplemente retornan
la palabra superior (High Word) y la palabra inferior (Low Word), respectivamente, de
un valor de 32 bits. En Win32 una palabra es un valor de 16 bits, haciendo
un DWord (palabra doble) un valor de 32 bits.
Para enviar un mensaje puedes
usar PostMessage( ) o SendMessage( ). PostMessage( ) pone el mensaje en la Cola
de Mensajes y retorna inmediatamente. Esto significa que una vez que la llamada
a PostMessage( ) se completa el mensaje puede o no puede haber sido procesado
an. SendMessage( ) enva el mensaje directamente a windows y no retorna hasta
que windows haya finalizado de procesarlo. Si quisieramos cerrar una ventana,
podramos enviarle a un mensaje WM_CLOSE de la siguiente
manera: PostMessage(hWnd,WM_CLOSE,0,0). Lo cual podra tener el mismo efecto que
hacer click en el boton cerrar de la ventana. Observa que wparam y lparam son
ambos 0. Esto es debido a que, como mencionamos, no se usan con el
mensaje WM_CLOSE.
Dilogos
Una vez que comiences a usar cajas de dilogo necesitars enviar mensajes a los
controles para poder comunicarte con ellos. Puedes hacer esto primero atravs
de GetDlgItem( ) (utilizando el ID como parmetro) para obtener el handle al control
y luego usar SendMessage( ). O puedes usar SendDlgItemMsg( ), el cual combina
ambos pasos. Como parmetros utilizamos el handle de la ventana y un ID de un hijo
y SendDlgItemMsg( ) retorna el handle del hijo y le enva el mensaje. Ambas formas
de enviar mensajes funcionan bien en todas las ventanas, no solamente en cajas de
dilogo.
Uso de Recursos
Puedes mirar los apndices al final del tutorial para mas informacin sobre el uso de
recursos con VC++ y BC++.
Antes de ver el tema en profundidad voy a cubrir el tema de recursos as no tendr
que reescribir el mismo en cada seccin. Por ahora no necesitas compilar el codigo,
solo es un ejemplo.
Los recursos son porciones pre-definidas de datos que se almacenan en formato
binario dentro de los archivos ejecutables y se organizan dentro de un archivo con
extensin ".rc" (resource script). Los compiladores comerciales cuentan con un editor
visual de recursos que permite crear recursos sin editar manualmente este archivo. A
veces la nica manera de poder crear un recurso es editar este archivo manualmente
sobre todo si tu compilador no tiene un editor visual o no soporta la caractersitica
exacta que necesitas.
Desafortunadamente, no todos los compiladores manejan los recursos de la misma
manera. Har lo mejor que pueda para explicar las caractersticas mas comunes que
son necesarias para trabajar con recursos en forma general.
El editor de recursos includo con MSVC++ hace muy difcil la tarea de editar los
recursos manualmente, debido a que fuerza un formato propio en ellos y si intentas
guardar uno que has creado manualmente estropea completamente el archivo. En
general no deberas preocuparte por crear archivos de recursos desde cero, pero
conocer como modificarlos manualmente puede ser muy til. Otra incomodidad es que
MSVC++ nombrar el archivo de recuros por default con el nombre "resource.h" an si
deseas llamarlo con otro nombre. Utilizaremos este con el propsito de simplicidad
para este documento, pero te mostrar como cambiarlo en el apndice sobre
compiladores.
Primero miremos un archivo de recursos bastante simple, con solo un cono:
#include "resource.h"
IDI_MYICON ICON "my_icon.ico"
Este es todo el archivo. IDI_MYICON es el identificador del recurso, ICON es el tipo de
recurso y "my_icon.ico" es el nombre del archivo externo que contiene al cono. Esto
debera funcionar en cualquier compilador.
Que hay acerca de #include "resource .h"?. Bien tu programa necesita una forma
de indentificar el cono y la mejor forma de hacerlo es asignndole a este un nico ID
(IDI_MYICON). Podemos hacer esto creando el archivo de cabecera "resource.h" e
incluir ste tanto en el archivo de recursos (.rc) como en el programa (.c)
#define IDI_MYICON
101
Como puedes ver, hemos asignado a IDI_MYICON el valor 101. De ahora en mas
podramos olvidarnos del identificador y simplemente usar 101 cada vez que
necesitamos referenciar el cono. PeroIDI_MYICON es un poco mas claro, mas
representativo y mas facil de recordar, sobre todo si estamos trabajando con una gran
cantidad de recursos.
Ahora supongamos que agregamos un recurso MENU:
#include "resource.h"
IDI_MYICON ICON "my_icon.ico"
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
END
END
Nuevamente IDR_MYMENU es el nombre del recurso y men es el tipo. Ahora observa
el BEGIN y el END. Algunos editores o compiladores utilizan la llave que abre { en lugar
de BEGIN y la llave que cierra } en lugar de END. Si tu compilador soporta ambos,
puedes elejir el que mas te guste si solo soporta uno u otro debrers hacer los
reemplazos necesarios en el codigo fuente para que esto funcione.
Tambin he agregado un nuevo identificador, ID_FILE_EXIT, por lo tanto necesitamos
agregarlo al archivo de cabecera de los recursos (resource.h) para poder usarlo dentro
de nuestro porgrama.
#define IDI_MYICON
#define ID_FILE_EXIT
101
4001
Generar y mantener un registro de todos los identificadores de recursos puede ser una
tarea costosa cuando trabajamos con grandes proyectos, esta es la razn por la que
muchas personas utilizan un editor visual de recursos, el cual se encarga de hacer todo
este trabajo por nosotros. Aunque pueden generar problemas de vez en cuando y
podras finalizar con multiples recursos con el mismo ID o problemas similares, es
bueno ser capaz de ir y poder corregir esos errores uno mismo.
Ahora veamos un ejemplo de como usar un recurso dentro de un programa.
HICON hMyIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYICON));
El primer parmetro de LoadIcon( ) y muchos otros recursos que utilizan funciones es
el handle a la instancia actual (la que obtenemos en WinMain( ) y que tambin puede
ser recuperada utilizando la funcin GetModuleHandle( ), como lo demostramos en
secciones anteriores). El segundo es el identificador del recurso.
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"
No hay demasiado aqu, pero nuestro men ser muy simple. Los nombres y valores
puedes escogerlos arbitrariamente. Ahora escribamos nuesto archivo .rc
#include "resource.h"
IDR_MYMENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", ID_FILE_EXIT
END
END
POPUP "&Stuff"
BEGIN
MENUITEM "&Go", ID_STUFF_GO
MENUITEM "G&o somewhere else", 0, GRAYED
END
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
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.
switch(LOWORD(wParam))
{
case IDOK:
EndDialog(hwnd, IDOK);
break;
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
Hay algunas diferencias importantes entre un Dialog Procedure y un Windows
Procedure. Una es que NO llamamos a DefWindowProc( ) para mensajes que no
manejamos. Con los dilogos esto es realizado automticamente.
Otra diferencia es que, generalmente, retornamos FALSE para aquellos mensajes que
no procesamos y TRUE para aquellos que si procesamos, A MENOS que el mensaje
especifique que valor debemos retornar. Observa que esto es lo que hicimos
anteriormente, por default, no debemos hacer nada y retornar FALSE, mientras que los
mensajes que si procesamos entran en el switch() y retornan TRUE.
La siguiente diferencia es que no llamamos a DestroyWindow() para cerrar el dilogo,
si no que utilizamos EndDialog( ). El segundo parmetro es el valor retornado a
quien halla llamado aDialogBox().
Por ltimo, en vez de procesar WM_CREATE, procesamos WM_INITDIALOG para realizar
cualquier proceso que sea necesario antes que aparezca el dilogo y luego
retornamos TRUE para darle el foco al teclado en el control asignado por default.
(Puedes procesar el mensaje WM_CREATE, pero ste es enviado ANTES que los controles
hayan sido creados, por lo tanto no puedes acceder a ellos. Cuando
procesamos WM_INITDIALOG los controles ya han sido creados, lo que nos permite
acceder a ellos).
Suficiente, hagmoslo...
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_HELP_ABOUT:
{
int ret = DialogBox(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
if(ret == IDOK){
MessageBox(hwnd, "Dialog exited with IDOK.", "Notice",
MB_OK | MB_ICONINFORMATION);
}
else if(ret == IDCANCEL){
MB_OK | MB_ICONINFORMATION);
}
else if(ret == -1){
MessageBox(hwnd, "Dialog failed!", "Error",
MB_OK | MB_ICONINFORMATION);
}
}
break;
// Other menu commands...
}
break;
Este es el cdigo que he usado para crear mi dialogo "About", seguramente te
preguntas por que est puesto dentro del manejador WM_COMMAND, si no tienes claro
este aspecto, puedes revisar la seccin de menus e iconos. ID_HELP_ABOUT es el
dentificador del item men Help\About.
Debido a que queremos que el men de nuestra ventana pricipal cree el dilogo,
obviamnete debemos poner este cdigo en el WinProc( ) de nuestra ventana
principal, no en el Dialog Procedure.
Ademas, he almacenado los valores de retorno de las llamadas a DialogBox( ),
solamente con el propsito de observar el efecto de presionar dentro del dilogo los
dos Botones, Esc, Enter, etc... y poder ilustrar como utilizar los valores de retorno de
un dilogo para chequear si tubo xito, hubo alguna falla o para ver la eleccin del
usuario o alguna otra informacin que elijas para envirsela de retorno a quien lo llame
desde el Dialog Procedure.
DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd,
AboutDlgProc);
IDD_ABOUT es el ID del recurso dilogo. hwnd el el handle a la ventana padre del
dilogo. AboutDlgProc( ) es, por su puesto, el Dialog Procedure usado para controlar
el dilogo.
Un astuto lector eventualmente podra haberse preguntado: si DialogBox() no retorna
hasta que el dilogo sea cerrado, entonces no podemos procesar mensajes mientras el
dialogo est activo, como funciona esto?. Bueno, el tema es que los dilogos tienen su
propio bucle de mensajes, por lo tanto mientras el dilogo esta siendo mostrado en
pantalla (esta activo), nuestro bucle de mensajes es pasado a un segundo plano y
empieza a ejecutarse el bucle que Windows utiliza por default. Este bucle es manejado
por Windows y tambin se encarga de cosas como mover el foco del teclado de control
en control cada vez que presionamos la tecla Tab.
Otro efecto de usar DialogBox( ) es que la ventana principal queda desactivada hasta
que el dilogo sea despachado. A veces esto es lo que deseamos y otras veces no,
como cuando queremos usar un dilogo con una barra de herramientas flotante. En
este caso queremos ser capaz de interactuar con ambos, la ventana y el dilogo. Pero
esto ser visto en la prxima seccin.
Dilogos de Modelamiento
Ejemplo: dlg_two
Ahora echmosle un vistazo a CreateDialog( ), la hermana de DialogBox( ). La
diferencia es que mientras DialogBox() implementa su propio bucle de mensajes y no
retorna hasta que el dilogo sea cerrado, CreateDialog() acta de manera mas
parecida a una ventana creada con CreateWindowEx(), la cual retorna inmediatamente
y depende de tu bucle de mensajes enviar los mensajes a tu ventana principal. Esto es
llamado Modelamiento mientras
queDialogBox() crea dilogos Modales.
Podemos crear el recurso dilogo de la misma
manera que lo hicimos en el ejemplo anterior,
tambin debemos asignar el estilo extendido
"Tool Window" (ventana de herramientas) para
darle a la barra de titulo el tpico ttulo pequeo
de las barras de herramientas. El recurso
dilogo que he creado es el siguiente:
IDD_TOOLBAR DIALOGEX 0, 0, 98, 52
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
EXSTYLE WS_EX_TOOLWINDOW
CAPTION "My Dialog Toolbar"
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON
"&Press This Button",IDC_PRESS,7,7,84,14
PUSHBUTTON
"&Or This One",IDC_OTHER,7,31,84,14
END
Habrs observado que el editor de recursos ha
reemplazado DIALOG con DIALOGEX indicando que queremos asignar un EXSTYLE en
nuestro dilogo.
A continuacin, queremos crear el dilogo cuando el programa se ejecuta y ademas
queremos que el dilogo sea visible, por lo tanto hacemos esto en WM_CREATE. Tambin
queremos declarar una varialbe global para almacenar el handle retornado
por CreateDialog( ), que usaremos luego. DialogBox( ) no retorna un handle
debido a que cuando ste retorna, el dilogo ha sido destruido.
HWND g_hToolbar = NULL;
case WM_CREATE:
g_hToolbar = CreateDialog(GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_TOOLBAR),
hwnd, ToolDlgProc);
if(g_hToolbar != NULL)
{
ShowWindow(g_hToolbar, SW_SHOW);
}
else
{
MessageBox(hwnd, "CreateDialog returned NULL", "Warning!",
MB_OK | MB_ICONINFORMATION);
}
break;
Es una buena idea chequear siempre los valores de retorno, y si son vlidos (distintos
de NULL) mostramos la ventana usando ShowWindow(), con DialogBox( ) esto no es
necesario debido a que el sistema llama a ShowWindow() por nosotros.
Ahora necesitamos un Dialog Procedure para nuestra barra de herramientas.
BOOL CALLBACK ToolDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM
lParam)
{
switch(Message)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_PRESS:
MessageBox(hwnd, "Hi!", "This is a message",
MB_OK | MB_ICONEXCLAMATION);
break;
case IDC_OTHER:
MessageBox(hwnd, "Bye!", "This is also a message",
MB_OK | MB_ICONEXCLAMATION);
break;
}
break;
default:
return FALSE;
}
return TRUE;
}
La mayora de las reglas de procesamiento de mensajes se aplican a los dilogos
creados con CreateDialog( ) y al igual que con DialogBox( ) no llamamos
a DefWindowProc( ), retornamos FALSEen los mensajes que no procesamos y TRUE en
los que si lo hacemos.
Un cambio es que, en los dilogos de modelamiento, no llamamos a EndDialog( ),
simplemente usamos DestroyWindow( ) como lo hacamos con las ventanas regulares.
En este caso, destrumos el dilogo cuando la ventana principal es destruda. En
el WndProc() de la ventana principal...
case WM_DESTROY:
DestroyWindow(g_hToolbar);
PostQuitMessage(0);
break;
Por ltimo, pero no menos importante, queremos ser capaz de mostrar y ocultar
nuestra barra de herramientas cada vez que el usuario lo desee, por lo tanto
agregamos dos comandos al men y los procesamos as:
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_DIALOG_SHOW:
ShowWindow(g_hToolbar, SW_SHOW);
break;
case ID_DIALOG_HIDE:
ShowWindow(g_hToolbar, SW_HIDE);
break;
//... other command handlers
}
break;
Hasta aqu, debes ser capaz de crear tu propio men usando el editor de recursos o
manualmente, si no es as (como pasa siempre) puedes observar el ejemplo dlg_two
que viene junto a este tutorial.
Cuando ejecutes el programa, debes ser capaz de acceder a la ventana de dilogo y a
la ventana pricipal al mismo tiempo.
Si ejecutaste el programa e intentaste correr el foco entre los dos botones usando la
tecla tab, probablemente habrs observado que esto no funciona, como asi tampoco
funciona activar los botones presionando Alt-O o Alt-P. Por que
no? DialogBox() implementa su propio bucle de mensajes y procesa estos eventos
por default, pero CreateDialog( ) no lo hace. Podemos hacerlo nosotros, llamando
a IsDialogMessage( ) en nuestro bucle de mensajes, el cual har los procesos por
default en lugar nuestro.
while(GetMessage(&Msg, NULL, 0, 0))
{
if(!IsDialogMessage(g_hToolbar, &Msg))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
Aqui, primero pasamos el mensaje a IsDialogMessage( ), si el mensaje est
destinado a nuestra barra de herramientas (indicado por el handle que le pasamos) el
sistema realizar los procesamientos por default y retornar TRUE. Si es el caso en que
los mensajes ya han sido procesados, entonces no llamamos
a TransalateMessage( ) o DispatchMessage( ) y si el mensaje es para otra ventana,
lo procesamos de la manera usual.
Es importante notar que IsDialogMessaje( ) tambin puede ser usado con ventanas
que no son dilogos, con la intencin de darles el comportamiento de un dilogo.
Recuerda que un dilogo es una ventana y la mayora (por no decir todas) de las API's
para dilogos funcionarn correctamente en cualquier ventana.
Un aspecto para destacar es que pasa si tienes mas de una barra de herramientas?
Bien, una posible solucin es tener una lista (o un arreglo) y ciclar en nuestro bucle de
mensajes pasando cada handle a IsDialogMessage( ) hasta que encontremos el
correcto y si no lo es, hacemos el procesamiento regular. Este es un problema de
programacin genrico y no uno que est relacionado con Win32, por lo tanto se deja
como ejercicio para el lector.
Controles
Una cosa para recordar acerca de los
controles es que son solo ventanas. Al
igual que cualquier otra ventana tienen
un window procedure, una clase
ventana etc... que es registrada por el
sistema. Todo lo que puedes hacer con
las ventanas normales, puedes hacerlo
con los controles.
Mensajes
Como recordars de las primeras discusiones sobre el bucle de mensajes, Windows se
comunica usando mensajes, envamos mensajes para que un control realice algo y
cuando un evento ocurre sobre un control, ste nos enva un mensaje de notificacin.
Para controles estndar esta notificacin ser un mensaje WM_COMMAND, como sucede
con los botones y menes y para los Controles Comunes, de los cuales hablar
despus, esta notificacin ser el mensaje WM_NOTIFY.
Los mensajes varan ampliamente para cada control, y cada control tiene su propio
conjunto de mensajes. En algn momento el mismo mensaje ser usado para mas de
un tipo de control, pero en general solo funcionarn en el control para el cual estn
destinados. Esto es especialmente molesto con los mensajes listbox y combobox (LB*
y CB* ) los cuales si bien realizan tareas casi idnticas NO SON intercambiables.
Por otro lado, los mensajes genricos como WM_SETTEXT son soportados por casi todos
los controles. Despus de todo un control es una ventana.
Puedes enviar mensajes usando la API SendMessage( ) y usar GetDlgItem( ) para
obtener el handle al control, o puedes usar SenDlgItemMessage( ) el cual realiza
ambos pasos. En ambos mtodos el resultado es el mismo.
Controles Edit
Uno de los controles mas comunmente usados en el entorno Windows, el control EDIT,
es usado para permitir al usuario ingresar texto, modificar, copiar, etc... El Block de
Notas de Windows no es mas que una ventana con un gran control edit dentro de ella.
Aqui est el cdigo usado como interface para el control edit de este ejemplo:
SetDlgItemText(hwnd, IDC_TEXT, "This is a string");
Esto es para cambiar el texto contenido dentro del control (esto puede ser usado para
cualquier control que tenga un valor de texto asociado a l, STATICs, BUTTONs, etc...).
Recuperar el texto de un control tambin es fcil, si bien es apenas mas trabajo que
cambiarlo...
int len = GetWindowTextLength(GetDlgItem(hwnd, IDC_TEXT));
if(len > 0)
{
int i;
char* buf;
buf = (char*)GlobalAlloc(GPTR, len + 1);
GetDlgItemText(hwnd, IDC_TEXT, buf, len + 1);
//Rellenamos con texto...
GlobalFree((HANDLE)buf);
}
Primero que todo, necesitamos reservar memoria para almacenar el string, para esto
necesitamos conocer cuanta memoria reservar. No hay una
funcin GetDlgItemTextLenght( ), pero existeGetWindowTextLenght( ), por lo tanto
todo lo que necesitamos hacer es obtener el handle al control usando GetDlgItem( ).
Ahora que sabemos la longitud, podemos reservar la memoria necesaria para
almacenar el string. Aqu, he agregado un chequeo para ver si existe algn texto (i.e la
longitud del string es mayor que cero) para no estar trabajando sobre un string vaco.
Asumiendo que existe dicho texto llamamos a GlobalAlloc( ) para reservar la
memoria. Si eres usuario de DOS/UNIX GlobalAlloc( ) es equivalente a calloc( ).
Esto reserva algo de memoria, inicializa su contenido a cero (0) y retorna un puntero a
dicha memoria. Hay diferentes flags que puedes pasar en el primer parmetro para
hacer que se comporte de manera diferente para diferentes propsitos, pero esta es la
nica manera en que la voy a usar durante el resto del tutorial.
Observa que le he sumado 1 a la longitud del string en dos lugares, Por que?
Bien, GetWindowTextLenght( ) retorna el nmero de caracteres que contiene el texto
del control SIN INCLUIR el terminador nulo. Esto significa que si no sumamos 1 el
texto podra caber pero el terminador nulo sobrepasara los limites del bloque de
memoria, causando una violacin de acceso o sobreescribiendo otros datos. Debes ser
cuidadoso cuando trabajas con longitudes de strings en windows, debido a que algunas
APIs y mensajes esperan que las longitudes incluyan el caracter nulo y otras no lo
hacen. Siempre lee la documentacin cuidadosamente.
Listas
Otro control muy usado son las listas. Este es el ltimo control estandar que voy a
cubrir por ahora, debido a que francamente no hay muchos interesantes y si an no
ests aburrido, yo si lo estoy :)
Agregado de Items
La primer cosa que quieres hacer con una lista es agregar items en ella.
int index = SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0,
(LPARAM)"Hi there!");
Como puedes ver, esta es una tarea muy simple. Si la lista tiene el estilo LBS_SORT, el
nuevo item ser agregado en orden alfabtico, en otro caso simplemente ser
agregado al final de la lista.
Este mensaje retorna el ndice del nuevo item, el cual podemos usar para realizar
alguna tarea sobre el tem, como asociar algunos datos a l. Generalmente esto es
como un puntero a una estructura conteniendo mas informacin, o quizs un ID que
usars para identificar el tem.
SendDlgItemMessage(hwnd, IDC_LIST, LB_SETITEMDATA, (WPARAM)index,
(LPARAM)nTimes);
Notificaciones
El proposito de las listas es permitir al usuario seleccionar cosas de una lista. Sin
embargo, a veces queremos ser capaz de hacer algo en el acto, quizs mostrar
informacin diferente o actualizada, basada en los items que fueron seleccionados.
Para hacer esto necesitamos procesar los mensajes de notificacin que nos enva la
lista. En este caso estamos interesados enLBN_SELCHANGE, el cual nos dice que la
seleccin ha sido modificada por el usuario. LBN_SELCHANGE es enviado
via WM_COMMAND, pero a diferenecia del procesamiento de los mensajes WM_COMMAND que
hacemos con los botones y menes, que son generalmente en respuesta a un click,
una lista enva el mensaje WM_COMMAND por varias razones y necesitamos un segundo
chequeo para averiguar que nos est diciendo. El Cdigo de Notificacin es pasado
como el HIWORD de wParam, la otra mitad del parmetro nos da el ID del control.
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_LIST:
// It's our listbox, check the notification code
switch(HIWORD(wParam))
{
case LBN_SELCHANGE:
// Selection changed, do stuff here.
break;
}
break;
// ... other controls
}
break;
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.
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.
Ejemplo: app_two
Dilogos Comunes
para archivos
El primer paso para abrir o guardar
archivos es obtener el nombre del
archivo a usar... aunque, simplemente
podramos poner el nombre del archivo
en el cdigo de nuestro programa, pero
honestamente esto no es muy til en la
mayora de los programas.
Debido a que esto es una tarea bastante
comn, existen dilogos predefinidos
por el sistema que pueden ser usados para permitir al usuario seleccionar el nombre
de un archivo. Los dilogos mas comunes, para abrir y para guardar archivos, son
accedidos a travs de GetOpenFileName( )and GetSaveFileName( ) respectivamente,
ambos de los cuales utilizan una estructura OPENFILENAME.
OPENFILENAME ofn;
char szFileName[MAX_PATH] = "";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn); // Mira la nota a continuacin
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Text Files (*.txt)\0*.txt\0All Files
(*.*)\0*.*\0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrDefExt = "txt";
if(GetOpenFileName(&ofn))
{
// Do something usefull with the filename stored in szFileName
}
Observa que en la estructura llamamos a ZeroMemory( ) para inicializarla a 0. Esto es
generalmente una prctica prudente, dado que muchas APIs suelen asignar los campos
que no utilizamos conNULL y de esta forma no necesitamos asignar cada miebro que
no utilizamos.
Puedes encontrar fcilmente el significado de cada uno de los campos de la estructura
en la documentacin. El campo lpstrFilter apunta a un string doblemente terminado
en NULL, puedes ver en el ejemplo que hay varios "\0" a travs de ste, incluyendo
uno al final... el compilador agregar el segundo al final, como siempre lo hace con los
strings constantes (esto es por lo que generalmente no necesitas ponerlos tu mismo).
Los NULLs en este string lo dividen en filtros, donde cada uno tiene dos partes. El
primer filtro tiene la descripcin "Text Files (*.txt)", la parte con la especificacin
Sin embargo, en Windows, todos stos mtodos finalmente llaman a las funciones de
la API Win32, las cuales usaremos aqu. Si ests utilizando otro mtodo para la
entrada/salida de archivos, entonces te resultar fcil entender el mtodo que vamos a
ver.
Para abrir archivos, podemos usar OpenFile( ) o CreateFile( ). MS recomienda
usar solamente CreateFile( ) debido a que OpenFile( ) es "obsoleta". CreateFile(
) es una funcim mucho ms verstil y provee un gran control sobre la forma en que
abrimos nuestros archivos.
Lectura
Digamos, por ejemplo, que le has permitido al usuario seleccionar un archivo
usando GetOpenFileName( )...
BOOL LoadTextFileToEdit(HWND hEdit, LPCTSTR pszFileName)
{
HANDLE hFile;
BOOL bSuccess = FALSE;
hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL);
if(hFile != INVALID_HANDLE_VALUE)
{
DWORD dwFileSize;
dwFileSize = GetFileSize(hFile, NULL);
if(dwFileSize != 0xFFFFFFFF)
{
LPSTR pszFileText;
pszFileText = GlobalAlloc(GPTR, dwFileSize + 1);
if(pszFileText != NULL)
{
DWORD dwRead;
NULL))
}
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;
&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.
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.
y luego agregamos los los bitmaps estndar a la barra de herramientas usando la lista
de imgenes en la libreria de los controles comunes...
tbab.hInst = HINST_COMMCTRL;
tbab.nID = IDB_STD_SMALL_COLOR;
SendMessage(hTool, TB_ADDBITMAP, 0, (LPARAM)&tbab);
Ahora que hemos cargado nuestras imgenes, podemos agregar algunos botones que
las utilicen...
ZeroMemory(tbb, sizeof(tbb));
tbb[0].iBitmap = STD_FILENEW;
tbb[0].fsState = TBSTATE_ENABLED;
tbb[0].fsStyle = TBSTYLE_BUTTON;
tbb[0].idCommand = ID_FILE_NEW;
tbb[1].iBitmap =
tbb[1].fsState =
tbb[1].fsStyle =
tbb[1].idCommand
STD_FILEOPEN;
TBSTATE_ENABLED;
TBSTYLE_BUTTON;
= ID_FILE_OPEN;
tbb[2].iBitmap =
tbb[2].fsState =
tbb[2].fsStyle =
tbb[2].idCommand
STD_FILESAVE;
TBSTATE_ENABLED;
TBSTYLE_BUTTON;
= ID_FILE_SAVEAS;
Barras de Estado
Algo que se suele encontrar a menudo en las aplicaciones son las barras de estado, la
pequea barra en la parte inferior de la ventana que muestra informacin. Son muy
fcil de usar, solo hay que crearlas...
hStatus = CreateWindowEx(0, STATUSCLASSNAME, NULL,
WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0,
hwnd, (HMENU)IDC_MAIN_STATUS, GetModuleHandle(NULL), NULL);
Y luego (opcionalmente) especificamos el numero de secciones que deseamos. Si no
especificamos ninguna, simplemente tendr una que usa el ancho entero de la barra.
Podemos especificar o recuperar el texto usando SetWindowText( ) como con lo
hacamos con muchos de los otros controles. Si especificamos ms de una seccin,
necesitamos especificar el ancho de cada una y luego usar SB_SETTEXT para especificar
el texto de cada seccin.
Para definir los anchos declaramos un arreglo de enteros donde cada valor es el ancho
en pixeles de una seccin. Si queremos que una seccin use el espacio restante
fijamos como valor -1.
El wParam nuevamente es para calcular cuantos elementos hay en el arreglo. Una vez
que hemos terminado de agregar secciones, fijamos el primer valor (de ndice 0) para
verla en accin.
Tamao Apropiado
A diferencia de los Menes, las barras de estado y de herramientas son controles
separados que viven dentro del rea cliente de la ventana padre. Por lo tanto si solo
dejamos nuestro cdigoWM_SIZE anterior, se van a solapar con el control edit que
hemos agregado en los ejemplos anteriores. En WM_SIZE, ponemos las barras de
estado y de herramientas en posicin y luego restamos el alto del area cliente para que
podamos mover nuestro control edit para rellenar el espacio restante...
HWND hTool;
RECT rcTool;
int iToolHeight;
HWND hStatus;
RECT rcStatus;
int iStatusHeight;
HWND hEdit;
int iEditHeight;
RECT rcClient;
// Size toolbar and get height
hTool = GetDlgItem(hwnd, IDC_MAIN_TOOL);
SendMessage(hTool, TB_AUTOSIZE, 0, 0);
GetWindowRect(hTool, &rcTool);
iToolHeight = rcTool.bottom - rcTool.top;
// Size status bar and get height
hStatus = GetDlgItem(hwnd, IDC_MAIN_STATUS);
SendMessage(hStatus, WM_SIZE, 0, 0);
GetWindowRect(hStatus, &rcStatus);
iStatusHeight = rcStatus.bottom - rcStatus.top;
// Calculate remaining height and size edit
GetClientRect(hwnd, &rcClient);
iEditHeight = rcClient.bottom - iToolHeight - iStatusHeight;
hEdit = GetDlgItem(hwnd, IDC_MAIN_EDIT);
SetWindowPos(hEdit, NULL, 0, iToolHeight, rcClient.right,
iEditHeight, SWP_NOZORDER);
Desafortunadamente esta es una porcin de cdigo bastante larga, pero es simple...
las barras de herramiemntas se autoposicionarn cuando envemos el
mensaje TB_AUTOSIZE y las barras de estado harn lo mismo cuando
enviemos WM_SIZE (las libreras de los controles comunes no entienden nada a cerca
de consistencia).
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.
extrao es porque erraste alguna de las alteraciones que vamos a hacer al programa
regular.
}
else
{
ejemplo solo crearemos un tipo de hijo, el cual ser como el editor de los ejemplos
anteriores.
BOOL SetUpMDIChildWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wc;
wc.cbSize
wc.style
wc.lpfnWndProc
wc.cbClsExtra
wc.cbWndExtra
wc.hInstance
wc.hIcon
wc.hCursor
wc.hbrBackground
wc.lpszMenuName
wc.lpszClassName
wc.hIconSm
=
=
=
=
=
=
=
=
=
=
=
=
sizeof(WNDCLASSEX);
CS_HREDRAW | CS_VREDRAW;
MDIChildWndProc;
0;
0;
hInstance;
LoadIcon(NULL, IDI_APPLICATION);
LoadCursor(NULL, IDC_ARROW);
(HBRUSH)(COLOR_3DFACE+1);
NULL;
g_szChildClassName;
LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&wc))
{
MessageBox(0, "Could Not Register Child Window", "Oh Oh...",
MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}
else
return TRUE;
}
Esto es bsicamente idntico a registrar nuestro Frame, no hay flags particularmente
especiales para usar con IMD. Debemos poner nuestro men en NULL y el window
procedure apuntando al window procedure de la ventana hija que escribiremos a
continuacin.
EnableFlag);
EnableFlag);
DrawMenuBar(g_hMainWindow);
}
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_FILE_OPEN:
DoFileOpen(hwnd);
break;
case ID_FILE_SAVEAS:
DoFileSave(hwnd);
break;
case ID_EDIT_CUT:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_CUT, 0,
0);
break;
case ID_EDIT_COPY:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_COPY, 0,
0);
0);
break;
case ID_EDIT_PASTE:
SendDlgItemMessage(hwnd, IDC_CHILD_EDIT, WM_PASTE, 0,
break;
}
break;
case WM_SIZE:
{
HWND hEdit;
RECT rcClient;
GetClientRect(hwnd, &rcClient);
}
return 0;
mensaje. Puedes buscar sobre DefMDIChildProc( ) en MSDN para encontrar una lista
sobre los mensajes que procesa y siempre asegurarte de pasrselos.
}
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;