miércoles, 8 de octubre de 2008

Mi primer programa en Win32

Comenzaremos con algo pequeño y veremos lo fácil que es utilizar directamente la API de Windows. Aprenderemos a mostrar un mensaje en pantalla y las características del código C++ en una aplicación en Windows. Para esto seguiremos estos 5 sencillos pasos:

1. Abrir el entorno de desarrollo (en mi caso Dev-C++)
2. Crear un nuevo proyecto Windows (ver apéndice B.1) que llamaremos Ejercicio1. Es importante hacerlo correctamente ya que si olvidas indicar que trabajamos en un proyecto Windows, no lograrás ejecutar el programa.
3. Agregar un Archivo C++ a nuestro proyecto (ver apéndice B.2) al que llamaremos Programa1.
4. Escribir el siguiente código en el archivo Programa1.cpp. Para ir adquiriendo confianza, te recomiendo transcribirlo completamente (sin hacer uso del Copy&Paste).

Programa1.cpp
#include

int WINAPI WinMain(HINSTANCE p1, HINSTANCE p2, LPSTR p3, int p4)
{
MessageBox(NULL, "¡Bienvenido a la programación Windows!", "Ejemplo", MB_OK);
return 0;
}

5. Generar el proyecto (ver apéndice B.3) y esperar un momento.

¡Increíble! Hemos sido capaces de mostrar un mensaje en pantalla completamente funcional como en la fig. 2.1.1. No estoy exagerando, ha sido un gran logro en tu aprendizaje. Lo que has hecho te hubiera tomado semanas enteras pero solo te ha tomado unos cuantos minutos gracias a que accedes a funciones de la API. Date cuenta que haz sido capaz de crear una ventana que cuenta con una barra de título (1), una etiqueta de texto (3) y dos botones (Aceptar 2 y Cerrar

4), el mensaje tiene buena apariencia, se puede mover por toda la pantalla, sin contar demás detalles, ¡esto pone interesante!.



fig. 2.1.1 Ejercicio1 ejecutándose.

En caso de haber errores: te mostrará un mensaje con una descripción (aunque a veces no sea de mucha ayuda). Todo varía dependiendo del tipo de compilador. El problema más común es que el compilador no este configurado para trabajar en un proyecto Windows y no encuentre la función main(). Al indicarle a nuestro entorno de desarrollo que trabajamos en un proyecto Windows, estas configuraciones necesarias se activan automáticamente (he ahí la importancia) y evitamos estos errores, te recomiendo visitar el apéndice B.

Otro posible error, que no te debería de ocurrir a estas alturas, es haber escrito mal el código. Verifica que has puesto los puntos y coma (;) en su lugar correspondiente, al igual que las llaves que abren y cierran ({ }), y no olvides que C++ distingue entre mayúsculas y minúsculas. Si este es tu caso, te recomiendo repasar algún manual de C++ y dejar este curso para otro momento.

2.2 #include
Esta es la primera línea de nuestro programa y nos servirá para incluir el archivo de cabecera windows.h que contiene todas las funciones, estructuras, macros y constantes numéricas que forman la API. Resumiendo, en todo proyecto es necesario incluir a windows.h para crear aplicaciones de Windows.

Si te ayuda, puedes verlo como un equivalente a las librerías estándar utilizadas en las aplicaciones de consola (ej. iostream).

El archivo de encabezado windows.h proporciona una ruta de acceso a más de mil declaraciones de constantes, declaraciones typedef y cientos de prototipos de funciones.

2.3 La función WinMain()
Ésta es la tercera línea de nuestro programa (contando la línea en blanco). En nuestras aplicaciones de consola utilizamos la función "main()" para indicar el punto de partida del programa. Esa función ya no resulta para aplicaciones Windows. WinMain() sustituye a la función "main()".

La función WinMain() es el punto donde inicia y termina la ejecución de una aplicación Windows. Al igual que el archivo de cabecera windows.h, todas las aplicaciones de Windows necesitan la función WinMain(). Esto es ventajoso ya que así podemos distinguir entre un programa diseñado para consola y un programa para Windows.

Recordemos como escribimos esta función:
int WINAPI WinMain(HINSTANCE p1, HINSTANCE p2, LPSTR p3, int p4)
{
...
}
Al finalizar la función WinMain(), se deberá devolver un número entero al sistema operativo, por lo que habremos de declararla como int.
La constante WINAPI, escrita entre el tipo de retorno y el nombre de la función, indica al compilador que la función WinMain() es la encargada de limpiar la pila y por el momento no necesitas comprender este hecho. Está definida como __stdcall dentro de windows.h (en windef.h). Puede suceder que leas algún otro código y encuentres que en lugar de WINAPI utilicen otras constantes como APIENTRY o PASCAL, son exactamente lo mismo. Lo que pasa es que todas ellas existen por cuestiones de compatibilidad de códigos (la evolución de las librerías, ¡por supuesto!), pero mi recomendación será siempre utilizar WINAPI que es la constante más reciente.

La función WinMain() cuenta con 4 parámetros. Con toda la intención del mundo y de manera didáctica, los parámetros (p1, p2, p3 y p4) tienen nombres extraños y nos dan una vaga idea de lo que almacenan (es una mala práctica que no debemos tener y que corregiremos más adelante).

Los dos primeros parámetros, p1 y p2, contienen un número único generado automáticamente por el sistema operativo al momento de ejecutar el programa (son del tipo HINSTANCE). El número de p1 nos sirve para referirnos a nuestra aplicación y diferenciarla de las demás. El número de p2 existe por cuestiones de compatibilidad con aplicaciones Win 3.x, pero por ahora basta decir que ya no se usa y que siempre guardará el valor cero (NULL).
El tercer parámetro p3 es un puntero hacia una cadena de caracteres terminada en cero (LPSTR o char*). En ella se guardarán los argumentos de secuencias de comandos indicados cuando se manda a ejecutar la aplicación (puede ser que sean administrados desde la consola, un acceso directo, el explorador de Windows u otro programa). Por ejemplo, si ejecutamos el programa desde la opción Menú Inicio / Ejecutar de esta forma: programa1.exe /AYUDA... la variable p3 contendrá la cadena "/AYUDA" (que podemos procesarla mediante código y darle un significado para decidir qué hacer con ella). Normalmente este parámetro contiene un NULL ya que al ejecutar el programa no se indican argumentos.1

El cuarto y último parámetro p4 es un número entero que representa alguna de las muchas constantes predefinidas de Windows, lo explicaré adelante en este capítulo. Este número indica la manera en que la ventana deba mostrarse, ya sea maximizada, minimizada, etc. Como ejemplo, este parámetro toma un valor cuando los accesos directos se configuran para abrir la ventana Maximizada o en algún otro estado en especial.

2.4 Acuerdos
Cuando diseñaron Windows, se utilizó una técnica especial para nombrar variables, constantes, clases, estructuras, etc: la Notación Húngara. Se le llama así en honor a su inventor, Charles Simonyi, de origen húngaro. Él propuso que cada nombre debe llevar un prefijo que identifique al tipo de dato, así por ejemplo, un programador cualquiera puede saber fácilmente el tipo de variable que se trata sin tener que buscar su declaración.

Aunque no es estrictamente necesario, es de mucha utilidad y lo veremos muy a menudo. Aquí va una lista de prefijos más utilizados para nombrar constantes y variables:

Prefijo Tipo de dato
b bool
c char (un solo caracter)
cb contador de bytes
d DOUBLE
fn función
h manipulador (handle)
l LONG
n int
p puntero
pt CPoint o POINT
str CString
sz cadena de caracteres terminada en cero (string with zero)
w WORD

Se utilizará a lo largo de todo el curso, así que no intentes aprenderte toda la tabla... mejor tómala de referencia y ya verás que con el tiempo identificarás casi inconscientemente su tipo. Generalmente se usa la primera letra o una abreviación del tipo de dato.

Ejemplos: nEdad es un número entero de alguna edad; hInstance es un manipulador de instancia (después veremos a que se refiere con "manipulador de instancia").
Se pueden realizar combinaciones mientras sea válido, por ejemplo, lp para long pointer o dw para double word. Así, lpszNombre indica que es un puntero largo que apunta a una cadena de caracteres terminada en cero y que contiene un nombre (aunque algunos optan por nombrarlo lpNombre para no hacerlo muy grande, lo correcto es ponerlo completo).
¡Alto!, ¿lp o puntero largo? en realidad es un simple puntero y ya. Antiguamente existían dos tipos de punteros: cortos (de 2 bytes) y largos (de 4 bytes). A partir de Windows 95 se usan solo los punteros de 4 bytes y ha quedado la costumbre de seguirles llamando largos.
Basados en esto, vamos a rescribir la función WinMain() como debería de ser:
int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
LPSTR lpszLineaCmd, int nEstadoVentana)
{
...
}
2.5 Tipo de datos y constantes
En la programación en Windows existen tipos de datos nuevos. En nuestro programa anterior tuvimos la oportunidad de trabajar dos: HINSTANCE y LPSTR. Como ya te habrás dado cuenta, estos tipos no existen en la programación en C++ estándar. ¿Por qué complicar más las cosas? Es por una razón muy simple, si se realizan actualizaciones al sistema, los programadores no tienen que cambiar sus códigos ni aprender las nuevas reglas (en teoría solo se remplazan los archivos de cabecera). Los tipos de datos están definidos por instrucciones typedef o #define.
Otra cosa, es muy común que las funciones trabajen y regresen números para indicar algo en específico, un ejemplo, el cuarto parámetro de la función WinMain(), nEstadoVentana, tomará el valor de 2 para indicar que la ventana debe mostrarse minimizada o 3 para indicar que debe ser maximizada. Obviamente no se espera que memorices el significado de cada número para cada función, por lo que se ha creado una lista de constantes que explican por sí solas la utilidad de la información contenida en el valor. En nuestro primer programa ya hemos utilizado dos de esas constantes: NULL y MB_OK (ambas valen cero). Puedes utilizar los números en vez de las constantes, da exactamente lo mismo, aunque probablemente pierdas mucho tiempo tratando de entenderlos para realizar modificaciones.
Sabiendo esto y si así lo deseas, puedes escribir el código de la siguiente manera (los cambios en negrita):
#include

int __stdcall WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
char* lpszLineaCmd, int nEstadoVentana)
{
MessageBox(0, "¡Bienvenido a la programación Windows!", "Ejemplo", 0);
return 0;
}
No lo recomiendo, pero sí lo puedes hacer. La desventaja es que el código será menos portátil y perderá legibilidad. Ahora tendremos que recurrir a la documentación de Windows para averiguar que significa el 0 en el cuarto parámetro de MessageBox(), a diferencia de utilizar su constante MB_OK que por lo menos te da una idea que se trata del botón Aceptar.
Vamos a estar continuamente manejando esas constantes numéricas. Volviendo al cuarto parámetro de la función WinMain(), no te diré que nEstadoVentana tomará el valor de 2 o 3 para indicar cómo se mostrará la ventana, en lugar de decirte eso, me referiré a que regresará valores SW_SHOWMINIMIZED o SW_SHOWMAXIMIZED, ¡más fácil!.
Si eres curioso puedes examinar los archivos de cabecera de windows.h, donde podemos encontrar sus siguientes declaraciones:
#define SW_SHOWNORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
¿Complicado? Las constantes se distinguen porque están escritas en MAYÚSCULAS, tienen un prefijo generalmente de dos letras (SW_, CS_, WM_, etc) y siempre es una buena idea usarlas. El prefijo indica la categoría o su finalidad como por ejemplo, SW_ quiere decir Show Window (mostrar ventana). Con el tiempo irás memorizando las más comunes y de ahora en adelante sabrás que la documentación de Windows es un apoyo inseparable, como veremos en otro capítulo más avanzado, donde te diré cómo y dónde conseguirla.

2.6 La función MessageBox()

Nuevamente, si buscamos en el archivo winuser.h (dentro de windows.h) encontraremos el prototipo de esta función:

int WINAPI MessageBoxW(HWND,LPCWSTR,LPCWSTR,UINT);

#define MessageBox MessageBoxW

La función MessageBox() tiene 4 parámetros de los cuales ahora solo nos interesan los últimos 3: el segundo y tercer parámetro son punteros hacia cadenas de texto (LPCWSTR); el segundo indica el contenido del mensaje a mostrar en pantalla, y el tercero es un string que se mostrará en la barra de título del mensaje. El cuarto parámetro es un número entero (UINT) en el cual indicaremos el estilo del cuadro de mensaje.

Hasta ahora al cuarto parámetro le hemos indicado el valor MB_OK (que sabemos vale cero) porque ese número indica que quieres mostrar el botón Aceptar. Si al cuarto parámetro le indicamos otro número, podremos ser capaces de mostrar un ícono de error o de pregunta, además de mostrar varios botones en lugar de solo uno. Esto no se hace al azar, ya he explicado que contamos con constantes numéricas para indicar qué botones y qué ícono mostraremos en pantalla. Aquí va una pequeña lista de constantes numéricas ya declaradas en windows.h y que podemos combinar para mostrar diferentes mensajes:

// BOTONES
#define MB_OK 0 //botón "Aceptar"
#define MB_OKCANCEL 1 //botones "Aceptar" y "Cancelar"
#define MB_ABORTRETRYIGNORE 2 //botones "Anular", "Reintentar", "Omitir"
#define MB_YESNOCANCEL 3 //botones "Sí", "No" y "Cancelar"
#define MB_YESNO 4 //botones "Sí" y "No"

// ICONOS
#define MB_ICONINFORMATION 64 //ícono de información
#define MB_ICONEXCLAMATION 0x30 //ícono de alerta, signo de exclamación
#define MB_ICONERROR 16 //ícono de error, alto, tacha roja
#define MB_ICONQUESTION 32 //ícono de pregunta
Para combinar dos constantes (ejemplo MB_ICONERROR y MB_OKCANCEL) debes utilizar el operador OR numérico, es decir, las juntas por medio del "|" (MB_ICONERROR | MB_OKCANCEL ... o también ... 16 | 1 ... o inclusive ... 17). ¿Muy sencillo no?, es una técnica que se utilizará en muchas funciones API para fusionar los significados de dos o más constantes, veamos un pequeño ejemplo:

#include

int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
LPSTR lpszCmd, int nEstadoVentana)
{
MessageBox(0,"¿Guardar cambios?", "Título", MB_YESNO | MB_ICONQUESTION);
return 0;
}
Nota aclaratoria: Hay ocasiones en que la suma de las constantes (MB_YESNO + MB_ICONQUESTION) rinden el mismo resultado que utilizando el operador OR numérico (MB_YESNO | MB_ICONQUESTION). Algunos programadores suman las constantes y les funciona, pero no es lo mismo y no debes seguir sus pasos. Para evitar errores, te recomiendo utilizar siempre el operador OR numérico "|", nunca sumes las constantes en tus códigos.
Supongo que a tu nivel deberías entender este "fenómeno". Con esto se busca que si por accidente se repite una constante, el resultado esperado no cambie (sabemos que MB_OKCANCEL | MB_OKCANCEL = MB_OKCANCEL), cosa que si se hubieran sumado hubiera resultado en otra cosa (MB_OKCANCEL + MB_OKCANCEL = MB_ABORTRETRYIGNORE ... sustituyendo... 1 + 1 = 2). ¡Utiliza |!

2.7 Ejercicios

1. Profundizando en la función MessageBox. Deberás mostrar un mensaje en pantalla, con la función MessageBox(), de manera que sea idéntico a la siguiente imagen (fig. 2.7.1):

fig. 2.7.1 Ejercicio de la función MessageBox().

2. Prueba de fuego. Esta es la prueba de fuego, supuestamente ahora eres capaz de crear y entender este programa:

Ejercicio1.cpp
#include
#include

int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
LPSTR lpszLineaCmd, int nEstadoVentana)
{
if (strcmp(lpszLineaCmd, "PASS:Windows") == 0)
MessageBox(NULL, "¡Contraseña aceptada!",
"Bienvenida", MB_OK | MB_ICONINFORMATION);
else
MessageBox(NULL, "Contraseña incorrecta", "Error", MB_OK | MB_ICONERROR);
return 0;
}
Pues sucede que si generas un ejecutable de ese programa, vas a Menú Inicio / Ejecutar y tecleas su ruta junto con la contraseña, como en la fig. 2.7.1, recibirás un mensaje como en la fig. 2.7.1. ¿Cómo ves? apenas estamos calentando motores y ya eres capaz de implementar una sencilla restricción en tus programas.
fig. 2.7.2 Menú Inicio / Ejecutar.

fig. 2.7.3 Mensaje de contraseña aceptada.
En caso de haber errores: Si te muestra siempre el mensaje de contraseña incorrecta, es por eso, no haz introducido correctamente la contraseña. Este error no te debería de pasar en este curso a este nivel, pues sabes que la función strcmp() compara el ASCII de sus dos argumentos y regresa 0 si son iguales. Por lo tanto, deberás escribir la contraseña tal y como fue programada (usando mayúsculas y minúsculas).

Otros posibles errores, son no haber configurado el código para compilar para Windows o haber escrito mal el código, ¡cuidado! quiere decir que no has comprendido el capítulo de inicio a fin.

2.8 Resumiendo.

Ya sabemos que siempre hay que incluir el archivo de cabecera windows.h y que el punto de partida de un programa Windows es la función WinMain(), la cual tiene cuatro parámetros de quienes podemos deducir brevemente sus significados si hacemos uso la notación húngara:
Parámetro Significado
HINSTANCE hInstancia manipulador de instancia
HINSTANCE hInstanciaPrev manipulador de instancia previa
LPSTR lpszLineaCmd puntero de cadena terminada en cero que apunta a la línea de comandos
int nEstadoVentana un número entero para el estado de la ventana

Los nombres por sí mismos nos dan una gran referencia de su finalidad, aun sin embargo, estoy consciente de las dudas que crean los dos primeros parámetros (¿qué es un manipulador? ¿qué es una instancia?) por lo que en los dos siguientes capítulos abriré espacios para explicar detenidamente esos conceptos y básicamente incrementaremos el nivel del curso para que no se quejen los listos.

Etiquetas:

Trabajando con ventanas en win32

Crear una aplicación en Windows puede parecer complicado, ¡y vaya que lo es al principio! Las codificaciones te parecerán interminables y confusas, te toparás con nuevos tipo de datos, términos incomprensibles y un sin fin de funciones nuevas... pero nada será en vano, debes pasar por aquí para disfrutar las ventajas de saber utilizar la API de Windows.

Cuando consigas ganar experiencia, te darás cuenta de que lo complicado no es mas que una mera apariencia, irás reconociendo funciones y sabrás qué usar y dónde buscar para realizar lo que tienes en mente. La experiencia se gana superando esa prueba mental que impone la programación en Windows.

En este capítulo trataremos algo complejo pero con lo que ya estás familiarizado: las ventanas. Es un tema básico de la programación en Windows, pues como ya sabemos, Windows traducido al español quiere decir "ventanas" y no deberá extrañarte saber que le dedicaremos capítulos enteros a ellas, eso sí, enfocado desde el punto de vista del programador.

Sé que ahora tienes muchas dudas sobre conceptos, tipos de datos, constantes... todo un mundo de información que necesito mostrarte en tan poco tiempo; no te preocupes, todo está fríamente calculado y vendrás resolviéndolas a medida que avanzas en el curso. Por ahora solo me enfoco en las bases, en lo principal, lo realmente útil; los detalles vendrán en su momento indicado.

Bueno, vamos a hacer costumbre esto de iniciar con un código. Para esto, seguiremos los 5 sencillos pasos del capítulo anterior

1. Abrir el entorno de desarrollo (en mi caso Dev-C++)
2. Crear un nuevo proyecto Windows (ver apéndice B.1) que llamaremos Ejercicio2. Es importante hacerlo correctamente ya que si olvidas indicar que trabajamos en un proyecto Windows, no lograrás ejecutar el programa.
3. Agregar un Archivo C++ a nuestro proyecto (ver apéndice B.2) al que llamaremos Programa2.
4. Escribir el siguiente código en el archivo Programa2.cpp. Para fines prácticos recomiendo usar el Copy&Paste, aunque no hay ningún problema si lo transcribes cuidadosamente.

Programa2.cpp

#include

/* La función ProcedimientoVentana() es necesaria porque es la
encargada de recibir los eventos (movimientos del ratón,
tecla, clics a un botón, etc). En este caso, solo monitorea
el momento el que el usuario decide cerrar la ventana para
descargar la aplicación de memoria */
LRESULT CALLBACK ProcedimientoVentana(HWND hwnd, UINT mensaje,
WPARAM wParam, LPARAM lParam)
{
switch (mensaje)
{
case WM_DESTROY:
PostQuitMessage (0);
return 0;
}
return DefWindowProc (hwnd, mensaje, wParam, lParam);
}



/* Esta cadena es necesaria para que la clase de ventana
y la función CreateWindowEx() se refieran a un mismo nombre */
char szNombreAplicacion[] = "Ejercicio2";

int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
LPSTR lpLineaCmd, int nEstadoVentana)
{
//Los 4 pasos para crear una ventana...
//1. Establecer los 12 campos de la "clase de ventana"
WNDCLASSEX ventana;

ventana.cbSize = sizeof(WNDCLASSEX);
ventana.style = CS_HREDRAW | CS_VREDRAW;
ventana.lpfnWndProc = ProcedimientoVentana;
ventana.cbClsExtra = 0;
ventana.cbWndExtra = 0;
ventana.hInstance = hInstancia;
ventana.hIcon = LoadIcon (NULL, IDI_APPLICATION);
ventana.hCursor = LoadCursor (NULL, IDC_ARROW);
ventana.hbrBackground = (HBRUSH) (COLOR_BTNFACE+1);
ventana.lpszMenuName = NULL;
ventana.lpszClassName = szNombreAplicacion;
ventana.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

//2. Dar de alta en el sistema a la "clase de ventana"
if( !RegisterClassEx(&ventana) )
{
MessageBox(NULL,
"No ha sido posible dar de alta a la ventana, abortando ejecución",
"¡Error!", MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}

//3. Crear la ventana en base a la "clase de ventana" anteriormente registrada
HWND hwnd;
hwnd = CreateWindowEx(
0,
szNombreAplicacion, //Nombre de clase de la ventana a la que pertenece
"Programa de ejemplo", //Título de la ventana
WS_OVERLAPPEDWINDOW, //Tipo de ventana
CW_USEDEFAULT, //Posición de la ventana en pantalla en "x"
CW_USEDEFAULT, //Posición de la ventana en pantalla en "y"
300, //Ancho
200, //Alto
HWND_DESKTOP,
NULL,
hInstancia,
NULL
);

if(hwnd == NULL)
{
MessageBox(NULL, "Error al crear la ventana, abortando ejecución",
"¡Error!", MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}

//4. Mostrar la ventana en pantalla
ShowWindow(hwnd, SW_SHOWDEFAULT);

//Necesario para manipular los mensajes o eventos de la ventana
MSG mensaje;
while(GetMessage(&mensaje, 0, 0, 0) == TRUE)
{
TranslateMessage(&mensaje);
DispatchMessage(&mensaje);
}

return mensaje.wParam;
}

5. Generar el proyecto (ver apéndice B.3) y esperamos un momento a que se muestre en pantalla el programa (ver fig. 3.1.1).

fig. 3.1.1 Programa2.cpp en ejecución.

¡Es mucho código para solo poder generar esto! No es cierto, hacer una ventana como esa sin usar las funciones API te habría tomado mucho tiempo. Hacer un programa para consola es relativamente más fácil que hacerlo para Windows, pero hoy en día un programa que no tiene interfase gráfica amigable no se usa ni se vende (salvo extraordinarias excepciones).
3.2 Cómo funciona una aplicación Windows

Una aplicación bajo un sistema gráfico como Windows trabaja de una forma muy diferente a las tradicionales. No podemos precisar el momento en que el usuario decida mover el puntero del ratón, dar un clic sobre un botón o minimizar la ventana. Por ello, el programa debe contar con un código que se encargue de recibir estos eventos en todo momento.

Windows está al pendiente de cualquier evento ya sea un movimiento del ratón, la pulsación de una tecla, el cambio de aplicación activa, la alteración del tamaño de una ventana, etc. Conforme se van presentando, los almacena en una lista que se le conoce como cola de mensajes.

¿Cómo puedo hacer que mi programa se de cuenta o tome control sobre estos eventos? Existen varias funciones API que en conjunto visitan la cola de mensajes y regresan los eventos que corresponden a la aplicación; como programadores podemos decidir qué hacer con estos eventos por medio de instrucciones de condición (if, switch). Es un tema que se profundizará en un capítulo posterior.

Te he introducido al tema de la cola de mensajes y los eventos porque en el código del Programa2, y en cualquier otro programa común de Windows, se utilizan estos conceptos. En el caso del Programa2, que aparentemente no hace nada, se encuentra monitoreando el cierre de la ventana (evento), es decir, cuando el usuario da un clic sobre el botón cerrar o cuando el usuario presiona Alt + F4. ¿Por qué monitorear ese evento? Porque cuando el usuario decide cerrar la ventana, nosotros como programadores queremos que finalice la aplicación.

Nota aclaratoria: Verás, cerrar una ventana y finalizar la aplicación son dos cosas completamente distintas. Esto puede parecerte raro ya que generalmente ambos se presentan al mismo tiempo (cuando cerramos una ventana se finaliza la aplicación), pero ello no implica que sean la misma cosa porque en realidad una aplicación puede seguir recibiendo eventos y seguir trabajando con o sin ventana. Por este motivo, monitoreamos cuando el usuario decide cerrar la ventana para generar otro evento que significa "dejar de procesar eventos/finalizar aplicación"; de no ser así, la aplicación continuaría activa en el sistema aunque no veamos ninguna ventana.

Basta decir que la programación en Windows está repleta de eventos, o sea, son un tema importantísimo. Imagina que se tiene un documento el cual no ha sido guardado, el programa puede monitoriar cuando el usuario desee cerrar la ventana para así mostarle al usuario un aviso "¡Si me cierras pierdes la información! ¿guardar cambios?". Calma, ¡ya tendremos tiempo para aprenderlo!
3.3 Crear una ventana

No quisiera extenderme en el tema de eventos sin antes ser capaces de responder a la siguiente pregunta... ¿cómo crear una ventana? Es algo un tanto complicado de explicar, aunque ya sabemos algunas cosas importantes: debemos incluir el archivo de cabecera windows.h y la función WinMain() que es el punto de partida de nuestro programa. Por razones obvias, ya te imaginarás que generalmente en el punto de partida es donde se decide crear una ventana (podrás comprobarlo en el código del Programa2).

Ahora bien, para crear una ventana hay que seguir 4 pasos. Primero decimos cuales son y en qué consisten y después nos dedicaremos a aclarar las dudas sobre conceptos, tipos de datos, etc. Los pasos son los siguientes:

1. Establecer los 12 campos de la clase de ventana
2. Dar de alta en el sistema a la clase de ventana
3. Crear la ventana en base a la clase de ventana anteriormente registrada
4. Mostrar la ventana en pantalla

1. Establecer los 12 campos de la clase de ventana. Antes de crear una ventana es obligatiorio establecer 12 configuraciones necesarias de la clase de ventana. Verás, una clase de ventana almacena una serie de atributos que definen el aspecto y comportamiento de una ventana. Algunos ejemplos sobre la información que contiene son el color de fondo, ícono, menú y un puntero hacia una función que será la encargada de procesar los mensajes de la ventana.

No te dejes llevar por su nombre ya que su denominación de "clase" no tiene nada que ver con la programación orientada a objetos, así decidieron llamarle, simplemente es una estructura que nos sirve para indicar los datos de iniciación de una ventana (o de una serie de ventanas). Examinando los archivos de cabecera de windows.h podemos ver su declaración:

typedef struct _WNDCLASSEXA {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEXA,*LPWNDCLASSEXA,*PWNDCLASSEXA;

typedef WNDCLASSEXA WNDCLASSEX,*LPWNDCLASSEX,*PWNDCLASSEX;

Observamos tipos de datos con los que no estamos familiarizados (UINT, WNDPROC, HANDLE, HICON...), no les tomes mucha importancia. Por ahora solo vamos a concentrarnos en los nombres de las variables involucradas ya que "esconden" información reelevante. Te he mostrado herramientas para que seas capaz de independizarte, así que vamos a deducir con la notación húngara lo siguiente:
Identificador Significado
cbSize contador de bytes de un tamaño
style estilo
lpfnWndProc puntero "largo" a una función de procedimiento de ventana
cbClsExtra contador de bytes extra de la clase
cbWndExtra contador de bytes extra de la ventana
hInstance manipulador de instancia
hIcon manipulador del ícono
hCursor manipulador del cursor
hbrBackground manipulador de la brocha para color del fondo
lpszMenuName puntero "largo" a la cadena del nombre del menú
lpszClassName puntero "largo" a la cadena del nombre de la clase
hIconSm manipulador de icono pequeño

Como puedes ver, la finalidad de algunos campos es muy descriptiva con solo leer su nombre. A pesar de esto, te introduciré a cada uno de ellos en su momento oportuno. Mientras tanto, te mostraré una sección de código del Programa2 donde tendrás la oportunidad de observar cómo se precisa esta "configuración" para crear una ventana. Te advierto que aparecen nuevas funciones, constantes y, por si fuera poco, otro nuevo tipo de dato (HBRUSH), no te preocupes por entender cada línea, solamente intento darte una vista muy general:

//1. Establecer los 12 campos de la "clase de ventana"
WNDCLASSEX ventana;

ventana.cbSize = sizeof(WNDCLASSEX);
ventana.style = CS_HREDRAW | CS_VREDRAW;
ventana.lpfnWndProc = ProcedimientoVentana;
ventana.cbClsExtra = 0;
ventana.cbWndExtra = 0;
ventana.hInstance = hInstancia;
ventana.hIcon = LoadIcon (NULL, IDI_APPLICATION);
ventana.hCursor = LoadCursor (NULL, IDC_ARROW);
ventana.hbrBackground = (HBRUSH) (COLOR_BTNFACE+1);
ventana.lpszMenuName = NULL;
ventana.lpszClassName = szNombreAplicacion;
ventana.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

Así es como se ha decidido iniciar los campos de la estructura WNDCLASSEX llamada ventana, es decir, de la clase de ventana. Los campos más importantes (en negritas) son lpfnWndProc que apunta hacia la función encargada de recibir y manejar los eventos de la ventana (clics, pulsaciones, movimientos, etc), y lpszClassName que contendrá el nombre de la clase de ventana (que será usada más adelante en el paso 3 en la función CreateWindowEx).

Como comentario adicional, puedes observar que en el campo style se combinan dos constantes (CS_HREDRAW y CS_VREDRAW) utilizando el operador OR, como vimos la sesión pasada.

Continuamos al paso 2. Dar de alta en el sistema a la clase de ventana:

//2. Dar de alta en el sistema a la "clase de ventana"
if( !RegisterClassEx(&ventana) )
{
MessageBox(NULL,
"No ha sido posible dar de alta a la ventana, abortando ejecución",
"¡Error!", MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}

A grandes rasgos, lo anterior llama a la función RegisterClassEx() para registrar la clase de ventana (la estructura de datos en el paso 1). Una vez registrada la clase de ventana, puedes crear tantas ventanas como quieras las cuales tendrán el mismo ícono, color, menú, etc.

Cuando las configuraciones no son aceptadas, la función regresa el valor cero y significa que no podemos crear la ventana. Generalmente sucede cuando hemos llenado incorrectamente alguno de los campos de la clase de ventana. En caso de que no sea aceptada la clase de ventana (no tiene caso seguir ejecutando la aplicación), finalizamos la aplicación con return FALSE por que una aplicación termina cuando termina la función WinMain() y ese return forza a que esto suceda. Observa que antes de que finalice la aplicación por acción del return, por cortesía se ha decidido presentar al usuario un mensaje que explique lo sucedido (se ha utilizado la función MessageBox() que vimos en el capítulo anterior).

3. Crear la ventana en base a la clase de ventana anteriormente registrada Aquí tienes que llamar a una función CreateWindowEx() donde también hay configuraciones que indicar, aunque estas son en su mayoría configuraciones del aspecto de la ventana. El pedazo de código:

//3. Crear la ventana en base a la "clase de ventana" anteriormente registrada
HWND hwnd;
hwnd = CreateWindowEx(
0,
szNombreAplicacion, //Nombre de clase de la ventana a la que pertenece
"Programa de ejemplo", //Título de la ventana
WS_OVERLAPPEDWINDOW, //Tipo de ventana
CW_USEDEFAULT, //Posición de la ventana en pantalla en "x"
CW_USEDEFAULT, //Posición de la ventana en pantalla en "y"
300, //Ancho
200, //Alto
HWND_DESKTOP,
NULL,
hInstancia,
NULL
);

if(hwnd == NULL)
{
MessageBox(NULL, "Error al crear la ventana, abortando ejecución",
"¡Error!", MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}

Quizás el parámetro más importante de la función CreateWindowEx() sea el segundo (en negrillas), ya que contiene el nombre de la clase de ventana a la cual pertenece. Si esta clase de ventana no existe o no ha sido aceptada por el sistema, tan sencillo como que no crea la ventana y en ese caso la función retorna un valor de NULL. Por eso, no es de extrañarse que después de la función el código tenga una condición que compruebe si el valor regresado por CreateWindowEx() es igual a NULL (hwnd == NULL), ya que si no es posible la creación de ventana se muestra un mensaje y se finaliza la aplicación (no tiene caso seguir ejecutándola).

Y por último, 4. Mostramos la ventana:

//4. Mostrar la ventana en pantalla
ShowWindow(hwnd, SW_SHOWDEFAULT);

En Windows es necesaria la función ShowWindow() para mostrar una ventana. El identificador de ventana creada por la llamada para CreateWindowEx() está alojado en el parámetro hwnd. Hay muchas posibilidades para mostrar la ventana como SW_SHOWMAXIMIZED que hace que la ventana este activa y ocupe toda la pantalla y SW_SHOWMINIMIZED.
3.4 Cambiar el color de fondo de la ventana

Antes que nada te explico que el objetivo de este apartado es que interactúes con el código y que te vayas soltando un poco. No te preocupes por entrar en detalles que por ahora no necesitas saber, los cómos y los porqués vendrán después. Hasta el momento, hemos logrado crear una ventana con un color que viene configurado en Windows (por defecto es gris claro como en la fig. 3.1.1), es decir, el color que el usuario ha configurado en "Propiedades de pantalla/Apariencia".

Como ya sabes, en el paso 1 definimos doce campos de la clase de ventana; uno de los campos es el que fija el color del fondo de la ventana (hbrBackground). Como ya estarás sospechando, para cambiar el color del fondo de la ventana tendremos que modificar el contenido de ese campo.

En el Programa2, la clase de ventana tiene el nombre de ventana. Vamos a localizar la línea que dice "ventana.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);" y después la modificaremos por una y solo una de las siguientes líneas:

//Hay varias formas de cambiar el color...
//========================================
//Utilizando los colores definidos por el usuario en "Propiedades
//de pantalla" (como el gris claro que usamos originalmente)
ventana.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
ventana.hbrBackground = (HBRUSH) (COLOR_BTNTEXT + 1);
ventana.hbrBackground = (HBRUSH) (COLOR_BTNHIGHLIGHT + 1);
ventana.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE + 1);
ventana.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1);
ventana.hbrBackground = (HBRUSH) (COLOR_HIGHLIGHT + 1);
ventana.hbrBackground = (HBRUSH) (COLOR_MENU + 1);

//Utilizando colores básicos
ventana.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
ventana.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );

//Creando un color específico producto de la mezcla de los 3
//colores básicos RGB(rojo, verde, azul)
ventana.hbrBackground = CreateSolidBrush( RGB(0xFF, 0xFF, 0xFF) );
ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0x00, 0x00) );
ventana.hbrBackground = CreateSolidBrush( RGB(0xFF, 0x00, 0x00) );
ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0xFF, 0x00) );
ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0x00, 0xFF) );
ventana.hbrBackground = CreateSolidBrush( RGB(0xC0, 0xC0, 0xC0) );

No creo que tenga pierde la indicación, es seleccionar alguna instrucción y reemplazarla en el código. Hecho el cambio, se vuelve a compilar el programa para observar los efectos. Si así lo deseas, puedes seguir practicando con cada una de las líneas anteriormente mostradas.
3.5 La función CreateWindowEx()

La función CreateWindowEx() tiene 12 parámetros (como mnemotecnica, la clase de ventana tiene 12 campos). El prototipo de la función es el siguiente:

HWND CreateWindowEx(
DWORD dwExStyle, // un tal "estilo extendido"
LPCTSTR lpClassName, // puntero al nombre de la clase a la que
// pertenece
LPCTSTR lpWindowName, // p. a la cadena que contiene el título
// de ventana
DWORD dwStyle, // estilo de ventana (características)
int x, // posición horizontal de la ventana
int y, // posición vertical de la ventana
int nWidth, // Ancho de la ventana
int nHeight, // Alto de la ventana
HWND hWndParent, // manipulador de ventana padre
HMENU hMenu, // manipulador del menú
HINSTANCE hInstancia, // manipulador de Instancia (2do param. WinMain())
LPVOID lpParam // puntero al valor lpParam
);

Por ahora puedes modificar los parámetros en negrillas, es decir, los parámetros 3, 5, 6, 7 y 8. Cuida que el tercer parámetro sea una "cadena de caracteres" y que los parámetros del 5 al 8 sean números enteros. Modifica y vuelve a compilar para observar los cambios.

Como puedes observar en el prototipo de la función, ésta regresa un tipo de dato HWND. Ese dato es muy útil para manipular a la ventana. Por ahora solo sabemos que ese dato regresado será igual a NULL cuando ocurre un error al crear la ventana (comunmente porque no existe la clase de ventana o aún no se ha dado de alta en el sistema satisfactoriamente). Es por ello que agregamos a nuestro código una línea de verificación (if(hwnd == NULL) { ... [fin de aplicación] ... }).
3.6 Ejercicios

1. Crear una ventana. Modifica el código del Programa2 para crear una ventana de 400x200 (ancho x alto), con el color de fondo rojo ( CreateSolidBrush(RGB(0xFF, 0x00, 0x00)) ), en la barra de título deberá mostrar "Programando en Windows"
fig. 3.6.1 Así debe quedar la ventana del ejercicio 1.

Etiquetas:

Introducción a la programación en windows 32

1.1 Introducción

Windows nos proporciona funciones a los programadores para realizar tareas comunes. Son importantes para nosotros porque nos ayudarán a despreocuparnos por el diseño de botones, cuadros de texto, menús, listados y nos aseguran que nuestra aplicación funcione con cualquier tipo de hardware sin importar el tipo de tarjeta gráfica, impresora, disco duro, etc. A este grupo de funciones se les conoce como API y se distribuyen junto con Windows. API: Application Programming Interface (interfaz de programación para aplicaciones).

Este curso pretende ayudar a todas aquellas personas que deseen comenzar a programar en Windows empleando directamente las funciones API. No es un camino fácil, más bien, es largo y empedrado, pero que conduce a la comprensión de las tantas cosas que ocultan Windows y sus intermediarios (MFC, OWL o Visual Basic, Delphi).

En su mayoría, el uso de estas funciones es un requisito para cualquier tipo aplicación de Windows. Por lo tanto, independientemente del lenguaje de programación utilizado, todo lo que aquí se explica es aplicable en códigos de C/C++, Java, Visual Basic y Ensamblador (lenguajes que cuentan con un compilador que soporta la "codificación Windows").

Opté por diseñar este curso porque he vivido las dificultades para entrar al mundo de la programación en Windows utilizando la API. La primera vez que intenté realizar una aplicación Windows me asusté tanto que no volví a intentarlo hasta dentro de dos años. En ese periodo de tiempo, tuve que conformarme programando en Visual Basic aunque los límites y la necesidad me orillaron a emplear la API, así que me armé de valor y fui aprendiendo "a golpes".

Ahora escribo este tutorial que es una guía paso a paso para introducirle en la programación a Windows por medio de la API. Espero que con este texto tome confianza y se acostumbre a saltar los obstáculos que presenta el manejo de la API cuando se tienen los primeros acercamientos. Deseo recopilar todo ese montón de ideas que necesita saber y mostrárselas de manera ordenada, combinando la teoría con la práctica, y empezando con lo más simple hasta alcanzar niveles limitados por mi conocimiento... solo necesito ánimo y mucho tiempo de dedicación para escribirlo.

Hay ciertas funciones de la API que no son compatibles o que inclusive no existen entre las diferentes versiones de Windows (las más conocidas Win3.x, Win95/98, WinNT, WinXP). Como mi intención es hablar de la programación en Windows en general, no tocaré funciones especiales y en caso de que se requiera, explicaré solo las diferencias relevantes. No encontrará información sobre Win3.x y versiones anteriores que considero obsoletas.

1.2 El lenguaje de programación que usaremos

En este curso, usaremos el lenguaje C++ y nuestro entorno de desarrollo oficial será Dev-C++ de BloodShed (el cual es un compilador público y gratuito que puedes descargarte de Internet en la siguiente dirección http://www.bloodshed.net/). Es necesario que tenga experiencia programando en C++, aunque no en la programación Windows.

Independientemente de donde trabajemos (Dev-C++, Visual C++.NET, Borland C++), todo el código aquí mostrado no deberá mostrar problemas para compilarse porque vamos a utilizar las librerías estándar. Cabe mencionar que no todos los compiladores están preparados para la codificación tipo Windows (y no de consola) dado que los ejecutables en Windows se ensamblan diferente.

Cabe aclarar que C++ no es el único lenguaje de programación que permite crear ejecutables que trabajan bajo Windows. Existen otros como Visual Basic, Fox Pro, Delphi... ¿Cuál es el mejor de todos? Esa pregunta es muy frecuente. Todo depende de lo que quieras llegar a hacer y del tiempo que dispongas para ello... es un tema muy extenso que por ahora no voy a profundizar. Aun sin embargo, todos los ejemplos e indicaciones del presente curso se realizarán en el lenguaje C++. No es un lenguaje fácil de dominar a comparación de Visual Basic u otros. Por lo que, si nunca ha programado y le es difícil encontrar a alguien que le oriente a elegir un lenguaje, debo decirle que Visual Basic es un lenguaje fácil de aprender con el que podrá realizar aplicaciones para Windows muy completas y en muy poco tiempo (digamos que es como la ruta fácil, pero igual es muy válida). Si ese es su caso, le aconsejo abandonar el curso.

1.3 ¿Por qué utilizar el lenguaje C++ para programar en Windows?

Habrá quienes tengan experiencia utilizando el lenguaje C++ y lo consideren como viejo y aburrido. Tal vez esto se deba a que una gran mayoría de los cursos de introducción a este lenguaje se basen en aplicaciones de consola (aquellas aplicaciones de pantalla negra y letras blancas) y que nuestras creaciones por su falta de diseño y la dificultad de uso asusten a cualquiera.

En realidad, el lenguaje C++ es muy versátil. Dadas sus características, se han desarrollado compiladores que soportan la codificación de los ejecutables de Windows y así podemos aprovechar de todas sus bondades que han hecho que sea uno de los lenguajes más respetados y utilizados por los expertos. Por ejemplo, el sistema operativo Windows, incluyendo la API y otras aplicaciones Microsoft, en su mayoría están escritos en C y C++. La lógica nos indica que si la empresa que encabeza el diseño del software en todo el mundo utiliza el lenguaje C/C++ es porque éste proporciona toda la potencia que se requiere al momento de realizar un programa.

Etiquetas: