Introducción a la Programación en Windows

 

Por: Demian C. Panello        demianpanello@yahoo.com.ar                  

Capítulo I

Indice rápido del capítulo 1:                                                              

Bien, en principio, cabe aclarar que en este curso no se verá en forma extensiva los innumerables tópicos que hacen a la programación bajo Windows. Existen numerosos conceptos técnicos como por ejemplo los hilos, que no serán tratados, ya que iniciar un estudio completo de un sistema operativo como Windows implicaría otro desarrollo muy diferente al que aquí se pretende exponer.

Se abordará simplemente el estudio del esquema típico de un programa Windows escrito en C++, de forma lo más sencilla posible, al alcance de cualquier persona con conocimientos de lenguaje C, por lo menos básicos.

 

Estructura del programa:

 

Cuando uno ejecuta una aplicación que corre bajo Windows y le presenta una ventana, ésta espera que el usuario "haga algo", presione algún botón del mouse, mueva el mouse, pulse una tecla, pulse algún botón que contenga la ventana, etc. Cuando finalmente el usuario "hace algo" la aplicación, (la ventana), responde de alguna forma, si se ha escrito código para que responda, (valga la redundancia). O sea, la ventana está continuamente esperando alguna acción del usuario, y entrará en acción algún proceso si el programador escribió alguna línea de código para tal acción.

La "acción", (o el "hacer algo"), del usuario se llama en Windows "mensaje", y el propio Windows es el encargado de "mandarle" a la aplicación los mensajes o acciones del usuario. Queda a criterio de la aplicación el responder o no a dicho mensaje.

Pensado en términos de programación, en algún lugar del código de la aplicación debería existir un bucle que itere continuamente, el cual, de alguna forma, debería procesar los mensaje que vayan llegando, hasta que justamente llegue el mensaje que significa terminar la ejecución, situación que debería dar por finalizadas las iteraciones del bucle.

O bien el bucle que procesa los mensajes podría plantear que se ejecute siempre y de repente salir del él cuando ocurre el mensaje de terminar la aplicación.

 La idea sería la siguiente.

 

Bucles posible para procesar los mensajes.

Mientras Mensaje <> Salir

               verificar tipo de mensaje.

Fin Mientras

Hacer siempre

           verificar tipo de mensaje (el mensaje de terminar la aplicación, hace salir del bucle)

Fin hacer

 

Y básicamente "verificar el tipo de mensaje" sería de alguna forma, identificar si se trata de una pulsación del botón izquierdo o derecho del mouse, si se movió el mouse, si se trata del mensaje que ocurre cuando se crea la ventana, si pulsó una tecla, etc.:

 

Caso Mensaje:

            caso "bóton izquierdo del mouse"

                        hacer algo

            caso "pulsó una tecla"

                       hacer algo

            caso "salir"

                       terminar la aplicación.

Fin Caso 

 

A simple vista parece que esto de esperar los mensaje funciona como los típicos menús de las aplicaciones para DOS, donde se presenta el menú con las distintas opciones y siempre estará activo hasta tanto no se haya optado por salir. Ni más ni menos esto de los mensajes funciona tal cual esas rutinas tan conocidas.

 

Esta bien, desde ya que la aplicación no sólo tendrá el código para manipular los mensajes, sino que también deberá haber creado antes la ventana.

 

Los pasos, en pseudo-código, a escribir para obtener una aplicación que corra bajo Windows serían:

 

- Crear la ventana.

- Si no se pudo crear, terminar y mostrar mensaje aclaratorio.

- Si se pudo crear, mostrarla y empezar el bucle de mensajes.

- Mientras no llegue el mensaje de salir, se verifica qué mensaje llegó y se escribe el código de respuesta (opcional).

- Llega el mensaje de salir, entonces se envía la notificación de salida.

- Sale del bucle y termina la aplicación.

 

La función WinMain():

 

Así como un programa de C para DOS tiene una función principal llamada Main(), un programa en C para Windows tiene la función WinMain().

Lo único que se necesita para crear una aplicación Windows sencilla, (una ventana), es un archivo .cpp, (se podría escribir el programa en un fuente con extensión .c, pero se estila usar la extensión de C++, o sea .cpp). La sintaxis es exactamente igual  que cualquier programa en C para DOS con la salvedad que obviamente se usarán funciones para el entorno Windows.

Las funciones necesarias se encuentran en el archivo de cabecera windows.h, que es el que vamos a incluir en nuestro programa.

Existen muchas formas de estructurar la aplicación, pero como mínimo deben existir 2 funciones:

 

- WinMain()    La función principal con la que arranca el programa.

- Una función encargada de verificar el mensaje llegado.

 

De esta forma en la función WinMain() se encontraría el código encargado de definir la ventana, de crearla y el bucle de mensajes, (dentro de él algo llamaría a la función encargada de verificar el mensaje).

 

Particularmente me gusta escribir además, una función para la definición y creación de la ventana, así que tendremos que escribir 3 funciones, (WinMain(), Inicializa() que define y crea la ventana y WindowProc() para manipular los mensajes).

 

La aplicación que vamos a escribir constará de una ventana y mostrará un mensaje en caso de pulsarse el botón izquierdo del mouse o el derecho.

 

Ingrese a Visual C++ y vaya al menú File - New, luego pulse la solapa Projects y seleccione Win32 Aplication. En Project Name escriba Win1, pulse OK y pasará al siguiente paso el cual pregunta ¿Qué tipo de aplicación quiere crear?, con las opciones "An empty project", "A simple Win32...", "A typical "Hello World"...". Deje seleccionada "An empty project" y pulse Finish. Se creará el proyecto y el Workspace, (el Workspace es el encargado de contener el proyecto).

 

Esto dio a lugar un proyecto vacío, tenemos entonces que agregar un archivo .cpp para escribir el código, vaya nuevamente a File-New y ahora pulse la solapa Files y marque C++ Source File. En File Name escriba win1  y pulse OK,(vea figura).

 

 

 

 

De esta forma se crea un archivo llamado win1.cpp en blanco, allí escriba:

 

//Creación de una ventana sencilla que muestra un
//mensaje de acuerdo a que botón del mouse se haya pulsado.
//Autor: Demian Panello.


#include <windows.h>
#define NOMBRE_CLASE "Ventana1" //Nombre de la clase ventana a crear
#define TITULO "Primer Ventana" //El título que mostrará la ventana


//Prototipos de funciones.

static BOOL Inicializa(HINSTANCE hInstance, int nShowCmd);

long FAR PASCAL WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

HWND hwnd; //Variable HWND, (window handler), global que será el manejador de la ventana.


//*********************** Función principal **************************
int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{
MSG msg; //Variable para los mensajes.

//Si no se puede crear la ventana retorno FALSE
if (!Inicializa(hInstance, nShowCmd))
    return FALSE;

//Bucle de mensajes.

while(GetMessage(&msg,NULL,0,0))
{

TranslateMessage(&msg);   //Se traduce los mensajes del teclado.
DispatchMessage(&msg);   //Manda el mensaje a la función encargada de manipularlo.

}
return msg.wParam;
}

//Esta función se encarga de definir los atributos, (propiedades de la ventana) y además la muestra
static BOOL Inicializa(HINSTANCE hInstance, int nShowCmd)
{
//Variable para registrar la ventana.
WNDCLASS wc;

//Propiedades de la ventana.
wc.style = CS_HREDRAW|CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra =0;
wc.cbWndExtra =0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE+1);
wc.lpszMenuName = NULL;
wc.lpszClassName =NOMBRE_CLASE;     //Nombre de la clase
RegisterClass(&wc);

//Se crea la ventana de clase "Ventana1"
hwnd=CreateWindowEx(WS_EX_APPWINDOW,NOMBRE_CLASE, TITULO, WS_TILEDWINDOW,CW_USEDEFAULT,0,
                                                CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);

if (!hwnd)     //Si no se pudo crear, retorno FALSE
   return FALSE;

ShowWindow(hwnd,nShowCmd);         //Muestro la ventana
UpdateWindow(hwnd);                             //Se dibuja la ventana,(mensaje WM_PAINT)
return TRUE;
}


//Esta función es la encargada de procesar los mensajes que van llegando
long FAR PASCAL WindowProc(HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
LPCTSTR Texto;    //Cadena para almacenar un texto

//Verifico que mensaje llegó(estos son sólo algunos mensajes)
switch(message)
{
case WM_ACTIVATEAPP:        //Se activó la aplicación
            break;
case WM_CREATE:                   //Se creó la ventana
            break;

case WM_KEYDOWN:             //Se pulsó una tecla
            break;
case WM_LBUTTONDOWN:                                                     //Se pulsó el botón izquierdo del mouse
            Texto="Pulsó el botón IZQUIERDO del mouse";
            MessageBox(NULL,Texto,"Un mensaje",MB_OK+MB_ICONINFORMATION);
            break;
case WM_RBUTTONDOWN:                                                //Se pulsó el botón derecho del mouse
            Texto="Pulsó el botón DERECHO del mouse";
            MessageBox(NULL,Texto,"Un mensaje",MB_OK+MB_ICONINFORMATION);
            break;
case WM_DESTROY:                                                         //Al destruirse la ventana invoco la salida
            PostQuitMessage(0);
            break;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}

 

 

Un poco extenso, ¿no?, bueno pero veamos de que trata cada cosa:

 

#include <windows.h>

 

Este es el archivo necesario a incluir para poder escribir aplicaciones que corran en Windows.

 

#define NOMBRE_CLASE "Ventana1"
#define TITULO "Primer Ventana"

 

Se definen dos constantes, una será el nombre que recibirá la clase de la ventana al momento de definirla y la otra será el título que mostrará la ventana.

 

static BOOL Inicializa(HINSTANCE hInstance, int nShowCmd);

long FAR PASCAL WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

HWND hwnd;

 

Como en todo programa de C en el cual uno a creado funciones, se escriben al principio los prototipos que le indican al compilador que más adelante se encontrará con el desarrollo de estas funciones. En este caso el primero es el prototipo de la función Inicializa() que será la función encargada de definir, crear y mostrar la ventana. El siguiente prototipo es el de la función WindowProc(), (puede tener cualquier otro nombre, pero comúnmente se suele usar WndProc o WindowProc), que será la encargada de verificar los mensajes que van llegando.

También se declara una variable global de tipo HWND que será el manejador de la ventana, (con esta variable se hace referencia a la ventana en cualquier lugar del programa).

 

int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)

 

Acá arranca la función principal, que equivale a Main() de los programas para DOS.

El retorno de la función principal es int PASCAL, (también se puede usar como retorno int WINAPI), y es eso, simplemente un int, (entero).

Recibe cuatro parámetros los dos primeros son de tipo HINSTANCE que identifican instancias de un objeto. Una instancia es un objeto al cual se le reserva memoria o ya está alojado. La variable hPrevInstance permite conocer si ya existe una instancia de la ventana en memoria, en el caso que ejecutemos por segunda vez la aplicación sin haber terminado la ejecución anterior. Se debería verificar dicha variable antes de crear la ventana, pero no obstante, aparentemente, Windows ahora gestiona automáticamente tal situación porque siempre viene con NULL.

El tercer parámetro es una variable de tipo LPSTR, (puntero a una cadena,...,¡bah!, una cadena), donde se almacena el parámetro pasado al ejecutable, en caso de haberlo ejecutado pasándole uno, (excluyendo el nombre del ejecutable); pero en caso de querer conocer todos los parámetros pasados se usa la función GetCommandLine().

El cuarto parámetro, de tipo int, representa como se mostrará la ventana, algunos posibles valores, (para el resto ver la ayuda de VC++):

 

SW_HIDE: No se visualiza la ventana.

SW_MINIMIZE: La ventana se muestra minimizada.

SW_SHOW: La ventana es visible.

 

Hay varias constantes más, recomiendo ver la ayuda de VC++, (que es imprescindible a la hora de programar).

 

Lo primero que se hace dentro de WinMain() es declarar una estructura MSG que será la encargada de guardar los mensajes y llamar a la función que crea la ventana, Inicializa(), que retornará TRUE en caso de crearla con éxito y FALSE en caso que no.

 

MSG msg;

 

La definición de la estructura MSG es:

 

typedef struct tagMSG { 

    HWND hwnd; 

    UINT message; 

    WPARAM wParam; 

    LPARAM lParam; 

    DWORD time; 

    POINT pt; 

} MSG;

 

El campo hwnd es el manejador de la ventana; message es el número de mensaje; (internamente Windows maneja los mensajes con números); wParam y lParam es información extra dependiente del mensaje, time es el momento en que llegó el mensaje y pt es una variable de tipo estructura POINT, (campo x y campo y) que almacenan la posición del cursor en coordenadas de la ventana donde ocurrió el mensaje. 

 

if (!Inicializa(hInstance, nShowCmd))
    return FALSE;

 

Los parámetros que se le pasan a Inicializa() son, la instancia, o sea el objeto a crear y la forma en que debe mostrarlo. En caso que retornase FALSE esta función, también  retornará FALSE la aplicación.

 

Si se pudo crear la ventana, entonces se pasa al bucle de mensajes:

 

while(GetMessage(&msg,NULL,0,0))
{

TranslateMessage(&msg);  

DispatchMessage(&msg);  

}
return msg.wParam;
}

La función GetMessage(), que esta evaluada en el while, retorna el número de mensaje, si justamente el mensaje es WM_QUIT, (que es como FALSE), sale del bucle.

Y los parámetros que se le pasan son: la variable msg de tipo MSG, (entonces GetMessage() carga la estructura con los valores para ese mensaje), el manejador de la ventana, (por lo general NULL) y los restantes son el mensaje más "chico" y más "grande" recibidos, en general 0 para los dos.

 

Dentro del bucle hay dos funciones; la primera, TranslateMessage() precisamente hace eso, "traduce", el mensaje del teclado recién llegado.  La segunda función se encarga, internamente, de llamar a nuestra función encargada de verificar los mensajes, o sea WindowProc().

 

El retorno de WinMain() en caso de llegar el flujo del programa al bucle de mensajes y de ocurrir WM_QUIT, es el código exit, caso contrario, (no se pudo crear la ventana), retorna 0.

 

Bueno, ahora veamos la función encargada de definir y crear la ventana.

 

static BOOL Inicializa(HINSTANCE hInstance, int nShowCmd)

Inicializa() retorna TRUE o FALSE en caso que haya podido o no crear la ventana. Y como ya dije, recibe como parámetros la instancia y la forma de mostrar la ventana.

Primero se declara una variable de tipo WNDCLASS, (se puede usar también WNDCLASSEX), que es una estructura que contiene los atributos de la clase ventana e inmediatamente se definen los mismos con valores.

 

WNDCLASS wc;

//Propiedades de la ventana.
wc.style = CS_HREDRAW|CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra =0;
wc.cbWndExtra =0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE+1);
wc.lpszMenuName = NULL;
wc.lpszClassName =NOMBRE_CLASE;    
RegisterClass(&wc);

 

Nuestra variable WNDCLASS es wc y los atributos posibles son:

 

typedef struct _WNDCLASS { 

UINT style; 

WNDPROC lpfnWndProc; 

int cbClsExtra; 

int cbWndExtra; 

HANDLE hInstance; 

HICON hIcon; 

HCURSOR hCursor; 

HBRUSH hbrBackground; 

LPCTSTR lpszMenuName; 

LPCTSTR lpszClassName; 

} WNDCLASS;

 

Los campos son:

 

style: El estilo de la ventana, se pueden combinar con |, en nuestro caso CS_HREDRAW|CS_VREDRAW indican que la ventana debe redibujarse si se cambia su tamaño a lo ancho y a lo alto, (ver ayuda VC++ para más info).

lpfnWndProc: Aquí se indica que función se encargará de verificar los mensajes, en nuestro caso WindowProc.

cbClsExtra: El número de bytes extras necesarios para ubicar la siguiente estructura de ventana. El sistema inicializa este campo con 0.

cbWndProc: El número de bytes extras necesarios para ubicar la siguiente instancia de una ventana. Es 0 para nuestro caso, (ver ayuda para más info).

hInstance: La instancia de una ventana, o sea la variable hInstance.

hIcon: El icono de la aplicación el cual se carga con la función LoadIcon(),  (ver ayuda para más información).

hCursor: El cursor, el cual se carga con la función LoadCursor(), (ver ayuda para más información).

hbrBackground: El color de fondo. Un valor de tipo HBRUSH, por ejemplo la constante COLOR_BTNFACE, COLOR_WINDOW, etc (ver ayuda para más información).

lpszMenuName: Puntero a una cadena, (eso es el tipo LPCTSTR), que contiene el recurso de menú. Aquí es NULL porque no tiene menú.

lpszClassName: Una cadena que representa el nombre de la clase, en nuestro caso es la constante NOMBRE_CLASE definida el principio del programa en la cláusula #define.

 

Después de definir los atributos de la ventana, se registra la clase con RegisterClass(&wc), (también se podría haber usado RegisterClassEx()).

 

Luego viene la siguiente porción de código:

 

hwnd=CreateWindowEx(WS_EX_APPWINDOW,NOMBRE_CLASE, TITULO, WS_TILEDWINDOW,CW_USEDEFAULT,0,
                                                CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);

if (!hwnd)     
   return FALSE;

ShowWindow(hwnd,nShowCmd);         
UpdateWindow(hwnd);                             

return TRUE;
}

 

Se crea la ventana con CreateWindowEx(), (se podría haber usado CreateWindow()), con varios parámetros:

 

HWND CreateWindowEx( 

DWORD dwExStyle, // estilo de la ventana, (extendido). 

LPCTSTR lpClassName, // el nombre de la clase que se acaba de registrar.

LPCTSTR lpWindowName, // título de la ventana.

DWORD dwStyle, // estilo de la ventana.

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, // manejador de la ventana padre. 

HMENU hMenu, // manejador de un menú.

HINSTANCE hInstance, // la variable hInstance.

LPVOID lpParam // puntero al valor de lpParam ); 

 

Para ver todos los posibles valores de cada parámetro vea la ayuda sobre CreateWindowEx().

 

Si no se puede crear la ventana se retorna FALSE, en caso contrario se muestra la ventana con ShowWindow() y se llama a UpdateWindow() que se encarga de dibujarla, (esta función envía un mensaje WM_PAINT).

 

Finalmente, la última función, la encargada de verificar los mensaje:

 

long FAR PASCAL WindowProc(HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)

El retorno es un entero largo de tipo PASCAL fuera del segmento de código, (FAR)..., un entero largo, no común y corriente, pero entero largo al fin. : )

Recibe cuatro parámetros: el manejador de la ventana, el mensaje, y las variables de información wParam y lParam.

 

El contenido de la función es:

 

{
LPCTSTR Texto; 

switch(message)
{
case WM_ACTIVATEAPP:        
            break;
case WM_CREATE:                   

            break;

case WM_KEYDOWN:             
            break;
case WM_LBUTTONDOWN:                                                     
            Texto="Pulsó el botón IZQUIERDO del mouse";
            MessageBox(NULL,Texto,"Un mensaje",MB_OK+MB_ICONINFORMATION);
            break;
case WM_RBUTTONDOWN:                                             

            Texto="Pulsó el botón DERECHO del mouse";
            MessageBox(NULL,Texto,"Un mensaje",MB_OK+MB_ICONINFORMATION);
            break;
case WM_DESTROY:                                                        
            PostQuitMessage(0);
            break;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}

 

En esta función se verifican los mensajes que van llegando por medio de un switch. No necesariamente uno debe evaluar todos los posibles mensajes sino aquellos que va a usar, aunque se recomienda evaluar, aunque no se haga nada WM_ACTIVATEAPP y WM_CREATE.

Lo único que se hace acá es ver si se pulsó el botón izquierdo del mouse, el derecho o si se está cerrando la ventana. En el caso de pulsar algún botón del mouse se muestra un mensaje por medio de la función MessageBox() cuyos parámetros son el manejador de la ventana que es "propietaria" del mensaje, (éste es NULL porque este mensaje no pertenece a nadie), luego viene el texto a mostrar, después el título y luego una combinación de botones e iconos a saber:

 

Botones posibles e iconos para MessageBox(), (en el caso de los iconos hay varias constantes para un mismo icono):

 

            BOTONES                                    ICONOS

MB_ABORTRETRYIGNORE MB_ICONEXCLAMATION,
MB_ICONWARNING
MB_OK MB_ICONINFORMATION, MB_ICONASTERISK
MB_OKCANCEL MB_ICONQUESTION
MB_RETRYCANCEL MB_ICONSTOP,
MB_ICONERROR,
MB_ICONHAND
MB_YESNO
MB_YESNOCANCEL

Si llegó el mensaje WM_DESTROY se envía el mensaje de salir con PostQuitMessage(), para salir "limpito".

Nuestra función que verifica los mensajes retorna el resultado de la función DefWindowProc() el cual depende del mensaje que se acaba de procesar.

Hemos llegado al fin del primer capítulo sobre la creación de aplicaciones Windows únicamente usando código. Desde ya que no se va a memorizar este modelo, sino que puede servir para ir probando cosas al momento de escribir el código. Por eso es que no pongo aquí para descargar los archivos fuentes de este ejemplo, (el que lo quiere que lo pida pulsando aquí), trate de escribirlo directamente en un archivo cpp mirando cada tanto aquellas cosas que no recuerde y así de a poco se va ir familiarizando con las funciones y variables.

Siguiente capítulo (2)

Regresar a la página principal.