Programación‎ > ‎C‎ > ‎6 - [Artículos]‎ > ‎

Principios básicos de programación Windows

INTRODUCCIÓN

Hola a todos. En esta ocasión vamos a aprender un poco de programación Windows bajo el lenguaje C++ utilizando la API Win32.
Este es un tema muy poco tratado, por lo tanto la documentación que existe en español es muy escasa, y la mayoría se limita solo a explicar de forma muy abstracta, por lo que nos podemos quedar perdidos un poco al tratar de entender.
Antes de empezar debo advertir que necesitaras un nivel intermedio de conocimiento del lenguaje C/C++ para poder comprender al 100%. ¿Por qué? Simplemente porque a pesar de que veamos grandes cantidades de líneas y algunas palabras raras y desconocidas, no deja de ser lenguaje C++ en ningún momento. Y sigue la misma estructura que un programa en modo Consola, con pequeñas diferencias que veremos a lo largo de este tutorial.

API DE WINDOWS

Una API (Application Programming Interface) es un conjunto de funciones, librerías, métodos e instrucciones que nos ofrece un programa para poder programar sobre él. En esta ocasión, el sistema operativo Windows, como todo buen S.O., necesita ofrecer a sus desarrolladores todas las funciones indispensables para así hacerle la vida más fácil a la hora de programar el software. Ejemplos claros de la API de Windows son por ejemplo los eventos de las ventanas (cerrar, minimizar, maximizar, restaurar), son funciones que ya vienen programadas y el desarrollador solamente se encarga de manejarlas.


CONCEPTOS BASICOS DE PROGRAMACION WINDOWS


Elementos de una ventana
Una de las características más importantes de todos los programas Windows, es que están formados por ventanas (de ahí el nombre Windows). Pero lo que no muchos saben, es que todos los elementos de un programa son ventanas, un botón de comando es una ventana, una caja de texto también es una ventana, una etiqueta también, en fin, todo lo que vemos está formado por ventanas. Veamos los elementos básicos de un programa:


En la barra de título se puede apreciar, a parte del título de la aplicación, su icono de identificación y los botones predefinidos minimiza, maximizar/restaurar y cerrar, los cuales vienen implícitos con el borde de ventana.

Programas Windows y el Sistema operativo
Cuando escribimos un programa Windows, éste pasa a ser ‘controlado’ por el sistema operativo. Es decir, nuestro programa no tratará directamente con el hardware, y todo lo que haga el programa pasara primero por el núcleo de Windows, y éste será quien decida qué hacer.
Para entenderlo más fácilmente, existen dos formas de programar para un sistema operativo: Modo Usuario y Modo Kernel. En el segundo modo, es donde si se tiene acceso al hardware, es en el cual se escriben los controladores para que funcionen los distintos dispositivos correctamente. Programar en dicho modo, es un poco más complicado, ya que se requiere también conocimiento de lenguajes de más bajo nivel y conocer la estructura de hardware a detalle. Los errores que se cometan en ese modo no los puede manejar el sistema operativo, y en ocasiones tiene que detener todo y provocar alguna excepción para no dañar el equipo, en el caso de Windows, una pantalla azul apagando el sistema dando los detalles del error.
Por esta razón, existe el modo usuario o modo protegido, aquí como mencioné antes, es el sistema operativo quien toma las decisiones, y nosotros nos encargamos de programar que es lo que queremos hacer. Al producirse algún error, simplemente el sistema nos lo informa y cierra el programa conflictivo.

Windows y los Eventos
Programar en Windows sigue la metodología de la programación orientada a eventos, esto quiere decir, que al ejecutar un programa, éste se queda esperando que algún evento ocurra y así poder decidir qué hacer. La mayor parte del código fuente de un programa Windows está enfocada en procesar estos eventos, los cuales pueden ser provocados por el usuario, por el mismo programa ó por un programa tercero.

Mensajes
Un evento en Windows ocurre por ejemplo, al dar clic izquierdo en el mouse, al presionar cierta tecla, o cuando un contador llega cero. Windows graba cada evento en un mensaje, el cual es almacenado en una cola de mensajes para ser procesado por la función de nuestro programa encargada de ello. Un mensaje es como llama Windows a cada evento o acción realizada y que tiene que ser procesada por el programa, es decir, al producirse un mensaje, el programa ejecuta el código asociado a ese mensaje, y después regresa el control al sistema operativo, pero el programa sigue activo esperando más mensajes, y no terminará hasta que reciba el mensaje de que debe cerrarse.

Función WindowsProc()
Todo programa Windows, necesita una función específica la cual se encargará de procesar los mensajes. Esta función es comúnmente llamada WindowsProc() o wndProc(), aunque no requiere un nombre especifico, ya que se asigna a través de un puntero cuando se crea y registra la ventana, pero es conveniente seguir el estándar para poder identificar esta función rápidamente en caso de un programa muy extenso.
Afortunadamente, no es necesario escribir un código para todos los mensajes que puedan ocurrir, solamente para los que realmente vamos a necesitar, es decir, si no necesitamos el click derecho, no es necesario manejar ese mensaje, todos los mensajes no filtrados son procesados al final sin ejecutarse ningún código la realizar la acción.

Tipos de Datos Windows
Para facilitarnos un poco más la programación, Windows define algunos tipos de datos especiales usados específicamente por la API, estos son:


Las únicas palabras que nos pueden parecer nuevas de esta tabla serian: manejador e instancia.
Como menciona la tabla, un handle (manejador), no es más que un en número entero de 32 bits. ¿Para qué se usa? Para identificar un objeto de todos los que residen en memoria, es decir, viene siendo un número único que distingue un objeto de otro. Estos los asigna el sistema operativo por cada objeto nuevo creado en memoria.
Por ejemplo, al ejecutar la calculadora de Windows, el programa se abre y dibuja una ventana con muchos botones y una etiqueta para mostrar los dígitos. Ahí, la ventana principal de la calculadora tiene su propio handle, cada botón también tiene el suyo, así como la etiqueta. Esto ayuda, por ejemplo a la hora de programar, saber que ventana envió algún mensaje, o para saber a qué objeto o ventana el sistema va a devolver un resultado. Lo veremos más adelante con un ejemplo para que quede totalmente claro el concepto y utilidad de los manejadores.
Lo que es una instancia, lo veremos más adelante en el ejemplo, pero se puede resumir como un programa ejecutándose ya en la memoria. Por ejemplo al abrir un programa y visualizar su pantalla, ya tiene su instancia, ya depende de nosotros si dejamos que se puedan crear tantas instancias del mismo programa o solo una a la vez.


Notación estándar en programas Windows.
Para poder ajustarnos a una buena práctica de programación, es recomendable utilizar prefijos a la hora de crear las variables, Microsoft recomienda utilizar la notación húngara para el desarrollo de programas Windows, es una forma sencilla de declaración de variables, que consiste en utilizar un prefijo, seguido del nombre de la variable, altamente recomendado que tenga relación con el uso que se la va a dar a la variable. Esto hará más fácil la modificación del programa en el futuro, así como una mejor comprensión del código para programadores terceros.



Y así fácilmente podremos crear los nombres de las variables, para poder identificarla
HANDLE hInstance;
DWORD dwIntensidad;[/PHP]


ESTRUCTURA DE UN PROGRAMA WINDOWS


Para poder escribir el programa mas pequeño basado en la API de windows, necesitarás principalmente dos funciones:


  • WinMain() Función principal con la que inicia el programa, es el equivalente a main() de los programas en modo consola. Es importante mencionar que la función debe llamarse exactamente así y no puede llevar otro nombre, lo que si podemos cambiar libremente son el nombre de sus parametros (pero no sus tipos).
  • WindowsProc() Función encargada de procesar los mensajes, su nombre puede ser modificado, pero no lo recomiendo para facilitar su identificación. Dependiendo de los mensajes, podria llegar a ser la función más grande del programa.

      Lo interesante de ésto, es que a pesar de que con solo esas dos funciones podemos realizar un programa, éstas no estan conectadas entre sí, es decir, WinMain() no manda llamar a WindowsProc(). Lo que en realidad sucede es que al crear una ventana dentro de WinMain(), uno de los parametros de la función que la crea es elegir la función encargada de procesar los mensajes. Entonces el sistema operativo, es el encargado de mandar llamar la función WindowsProc() cuando es necesario, una preocupación menos ^_^



      Bien, una vez que haya quedado claro lo anterior, llega la hora de escribir el código y empezar a practicar lo aprendido. Pero, solo una cosa más... como sabe el compilador los nuevos tipos de datos, y esos nombres de funciones que no estan en el estandar... Respuesta: windows.h

      En el archivo de cabecera windows.h, se encuentran declaradas otras cabeceras importantes para que funcionen los programas basados en windows, por ejemplo: winbase.h, winuser.h, wingdi.h, winerror.h entre otras más, que por ahora no es necesario conocer a fondo. Pero que contienen funciones como WinMain() GetMessages() TranslateMessages() DispatchMessages() que como mencioné son indispensables para escribir nuestro programa.


NUESTRO PRIMER PROGRAMA WINDOWS

Ahora si, a poner en práctica lo aprendido. En teoría podemos usar cualquier compilador que genere ejecutables de 32 bits para windows. Pero por comodidad y facilidad usaré el IDE Code::Blocks, el cual incorpora el compilador MinGW, mas que suficiente para este ejemplo.

Este IDE cuenta con un asistente, que con un par de clicks puede crear un proyecto Win32 facilmente, pero como vamos a prácticar lo aprendido, la unica forma de aprender es haciendolo desde 0. Asi que comenzemos.



  • Abrimos  code::blocks
  • Crearemos un nuevo archivo desde el menu File/New/File...


  • Elegimos C/C++ Source (Archivo Fuente C/C++) y despues click en Go..
      .

  • En este caso es indiferente elegir C o C++, pero por compatibilidad con futuros cambios y mejor compatibilidad al estandar elegiremos C++, click en next.

  • Por ultimo escribimos un nombre para el archivo y seleccionamos la ruta donde se guardará, no vamos a añadir el archivo a un proyecto ya que será el unico fuente. Click en finish para que termine el asistente y nos muestre nuestra area de trabajo:



Función WinMain típica.
La función WinMain, a diferencia de main(), necesita obligatoriamente cuatro parametros, generalmente es declarada asi:
Código

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
 
}



La macro WINAPI, indica que es una función windows, esto la hace un tanto especial, y es ahi cuando el compilador se da cuenta que estamos generando un ejecutable especificamente para Windows con interfaz gráfica.



  • hInstance: es un manipulador para la instancia del programa que estamos ejecutando. Cada vez que se ejecuta una aplicación, Windows crea una Instancia para ella, y le pasa un manipulador de dicha instancia a la aplicación.
  • hPrevInstance es un manipulador a instancias previas de la misma aplicación. Como Windows es multitarea, pueden existir varias versiones de la misma aplicación ejecutándose, varias instancias. En Windows 3.1, este parámetro nos servía para saber si nuestra aplicación ya se estaba ejecutando, y de ese modo se podían compartir los datos comunes a todas las instancias. Pero eso era antes, ya que en Win32 usa un segmento distinto para cada instancia y este parámetro es siempre NULL, sólo se conserva por motivos de compatibilidad.
  • lpCmdLine, esta cadena contiene los argumentos de entrada de la linea de comandos.
  • nCmdShow, este parámetro especifica cómo se mostrará la ventana. Para ver sus posibles valores consultar valores de ncmdshow. Se recomienda no usar este parámetro en la función ShowWindow la primera vez que se ésta es llamada. En su lugar debe usarse el valor SW_SHOWDEFAULT.

Como mencioné antes, podemos cambiar los nombres de los parametros, pero no los tipos, aun así es preferible dejarlos tal cuales, y es lo que voy a hacer.

La función WinMain() necesita hacer 4 cosas:

  • Especificarle al sistema operativo que tipo de ventana se requiere
  • Crear la ventana
  • Inicializar la ventana
  • Manejar los mensajes (prepara la cola de mensajes que WindowsProc() va a procesar y los va enviando conforme van ocurriendo esperando un resultado)

PASO 1

El primer paso al crear una ventana, es definir y especificar el tipo de ventana que necesitamos. Esto es sencillo. Para ello, Windows define un tipo especial de estructura (struct) llamado WNDCLASSEX, que contiene toda la información para especificar los parametros de una ventana, que de ahora en adelante llamaremos clase de ventana (No confundir con una clase de C++).
Entonces lo que necesitamos hacer es crear una variable de tipo WNDCLASSEX, y asignarle valores a cada uno de sus miembros (como si estuvieramos estableciendo las propiedades de un formulario).  Una vez asignados los valores, tenemos que pasarlos al sistema operativo a través de una función que veremos mas adelante, a eso se le llama Registrar una clase. Ya cuando esté registrada, Windows puede manejarla y podemos asignarla a una ventana que crearemos en el proximo paso.

Antes de escribir el código que especifica y registra la clase de ventana que vamos a usar, voy a explicar la estructura WNDCLASSEX  y sus miembros, la cual está definida en el archivo winuser.h de la siguiente forma:
Código

typedef struct _WNDCLASSEXA
{
   UINT cbSize;                    //Tamaño de la estructura en bytes
   UINT style;                      //Estilo de la ventana
   WNDPROC lpfnWndProc;//Puntero a la función que procesa los mensajes
   int cbClsExtra;//Especifica cuantos bytes extra se reservarán a continuación de la estructura de la clase
   int cbWndExtra;             //Especifica cuantos bytes extra se reservarán a continuación    de la instancia de la ventana
   HINSTANCE hInstance;    //Instancia de la ventana a la que pertenece esta clase
   HICON hIcon;               //Icono de la ventana
   HCURSOR hCursor;         //Cursor que tomará el mouse sobre la ventana
   HBRUSH hbrBackground;  //Pincel que utilizará la ventana
   LPCSTR lpszMenuName; //Puntero a una cadena terminada con cero que especifica un nombre de recurso de la clase menú
   LPCSTR lpszClassName;  //Puntero a el nombre de la clase
   HICON hIconSm;           //Icono pequeño asociado a la ventana
}


Bien, ya que sabemos como está estructurada la variable, procedamos a crearla en nuestro programa y ajustar todos los miembros de acuerdo a nuestras necesidades. La siguiente imagen nos muestra como debe quedar el código, en seguida lo explicaré:



Bueno, primero que todo, ese error en la linea 10. Como mencioné antes, la propiedad lpfnWndProc, es un puntero a la función que procesa los mensajes. Dicha función es WindowsProc(), la cuál no he definido. Para hacerlo más sencillo de entender, vamos a  colocar la funcion WindowsProc() antes que WinMain(),

La API Win32 especifica que el prototipo de la función que procese los mensajes es el siguiente:


LRESULT CALLBACK WindowsProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

Debe ser tipo LRESULT, por que retorna los resultado de los mensajes, que no es más que un entero largo de 32 bits. También, debido a que es un puntero a función, debe ser declarado CALLBACK, ya que asi lo especifica el estandar de la API win32.

Los parametros que recibe son:
HWND hwnd: Es el manejador de la ventana que envía el mensaje, una vez que termine de procesar los mensajes que recibe, le devuelve el control al manejador para que continue con lo suyo.
UINT msg: El identificador de un mensaje es siempre un numero natural sin signo (Unsigned INT), esto hace más fácil el proceso y manejo de los mensajes, es por eso que son recibidos a través de este parametro.
WPARAM wParam y LPARAM lParam: Como existen varios tipos de mensajes, estos parametros ayudan a procesar los mensajes. Ya que algunos mensajes pueden contener un parametro adicional al ser procesadors. Pueden ser tipo WORD (entero sin signo) para WPARAM ó LONG (entero de 32 bits con signo) para LPARAM).

Afortunadamente, nosotros no tenemos que preocuparnos de estos parametros, ya que existe una función encargada de pasar los mensajes a Windows con todo lo necesario para ser procesados,  y además tambien espera e interpreta el resultado de cada mensaje. Esta función es DispatchMessage(), la cual veremos un poco más adelante.

Por lo pronto, el programa nos queda de la siguiente manera:



A lo largo de éste código observamos algunas constantes, como CS_HREDRAW, CS_VREDRAW, IDI_APPLICATION, IDI_ARROW y COLOR_WINDOW
Asi como 3 funciones: LoadIcon(), LoadCursor() y RegisterClassEx().

Las constantes que puede recibir la propiedad style son las siguientes:
Todas son enteros de 16 bits, que pueden ser combinadas con el operador OR (|) para ajustar el estilo deseado.



Asi como esas, existen cientos de constantes mas declaradas en los distintos archivos de cabecera que utiliza windows, sería imposible mencionarlas todas. Además es una gran ventaja que en C++ ya estén declaradas, ya que por ejemplo en Visual Basic 6 teniamos que asignar el valor de las constantes manualmente cuando necesitabamos trabajar con la API de Windows. Por lo tanto explicaré las 3 restantes que aparecén hasta ahora:
IDI_APPLICATION = Constante que indica que se usará el icono estandar de aplicacíon Win32 para el programa
IDC_ARROW = Constante que indica que se usará el cursor del mouse estandar, es decir, la flecha que esté como predeterminada.
COLOR_WINDOW = Constante cuyo valor indica el color predeterminado de las ventanas de Windows

Las funciones LoadIcon() y LoadCursor(), sirven para cargar tanto el icono como el cursor al programa. Ambas reciben los mismos parametros que son: (Instancia actual, Nombre del recurso). En este caso, obviamos la instancia actual, ya que es la única, y usamos las constantes predefinidas en Windows para el nombre del recurso, asi el sistema identifica cual icono y cursor debe cargar sin problema.

RegisterClassEx()
Esta función registra una clase de ventana para su   uso subsecuente en llamadas a las funciones de creación de las ventanas, como CreateWindow(), que veremos mas adelante, esta recibe como parametro la dirección de la estructura de la clase creada.


PASO 2

Una vez que Windows sabe que clase de ventana va a utilizar, es hora de crear la ventana.
Primero tenemos que declarar un manejador para la ventana, este debe ser un HANDLE para ventanas, definido en el estandar como HWND.
Despues invocamos a la función CreateWindow(), la cual recibe 11 parametros y devuelve un HWND

Los función CreateWindow() está definidia en winuser.h y su prototipo es:

HWND CreateWindow( 
   LPCTSTR lpClassName,   // Puntero al nombre de la clase registrada
   LPCTSTR lpWindowName,  // Puntero al nombre de la ventana
   DWORD dwStyle,         // Estilo de 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 o propietaria
   HMENU hMenu,           // Manejador de menú o identificador de ventana hija
   HANDLE hInstance,      // Manejador de la instancia de la aplicación
   LPVOID lpParam         // Puntero a los datos para la creación de la ventana
  );


Despues de crear la ventana, hay que mostrarla en pantalla, para ello utilizaremos otra función de la API, la cuál es: ShowWindow(), ésta solo recibe dos parametros, su definición es:
Código

 BOOL ShowWindow( 
  HWND hwnd, // Manejador de ventana
  int nCmdShow // Modo de visualización de ventana
);


Una vez conociendo los parametros, será facil ajustarlos en el programa, al momento de crear la ventana. Vamos a crear una ventana sencilla:


Aqui hay que destacar un parametro muy importante, el tercero. Indica el estilo que tomará la ventana, es decir, si se puede redimensionar, mover, si tiene barra de titulo, bordes, botones de maximizar, cerrar y restaurar, etc.... La siguiente tabla detalla las constantes que pueden ser tratadas en ese parametro:


Podemos crear y mostrar tantas ventanas necesitemos en nuestra aplicación, incluso, como mencioné al principio, con este metodo tambien crearemos los botones, cajas de texto, etiquetas entre otros controles que pueden ser usados en nuestros programas.
Ahora el siguiente paso es inicializar la ventana.


PASO 3

Despues de llamar a la función ShowWindow() la ventana se muestra en pantalla, pero aun no tiene contenido. Asi que primeramente, necesitamos habilitar la ventana para que se pueda dibujar en ella, es decir, en el area de cliente de la ventana.

La función UpdateWindow() actualiza el área de cliente de la ventana especificada enviándole un mensaje determinado (WM_PAINT) a ésta si la región de actualización no está vacía. La función envía dicho mensaje directamente al procedimiento de ventana de la ventana especificada, evitando la cola de la aplicación. Si la región de actualización está vacía, no se envía mensaje.

Esta función solamente necesita como parametro el manejador de la ventana que necesita actualizar. Dejandola totalmente inicializada para poder dibujar en ella. Asi que este paso 3 solo tendrá una sola linea:



PASO 4

Llegamos al corazón de la aplicación. Ahora toca controlar la cola de mensajes que recibe la ventana, para que se envien a la funcion WindowsProc().
El algoritmo es muy sencillo:
Código:

Mientras la cantidad de mensajes en la cola sea mayor a cero
     Traducir el mensaje
     Enviar el mensaje a la función que los procesa
Fin mientras
Regresamos el control a windows


GetMessage()
Básicamente, el programa debe estar al pendiente de los mensajes que reciba, para que los enve a WindowsProc(), y ésta se encarga de procesar el evento asociado a cada mensaje, termina de hacerlo, regresa el control a la aplicación, y se queda en espera de mas mensajes, asi sucesivamente hasta recibir el mensaje que cierra el programa.
¿Pero como sabemos si hay mensajes?. La API nos proporciona la función GetMessage().
Esta función recupera un mensaje de la lista de mensajes de la aplicación y lo coloca en la estructura especificada. La función recuperará los mensajes en el rango especificado. Pero no recuperará mensajes de ventanas que pertenezcan a otros procesos o aplicaciones.

Su prototipo recibe cuatro parametros y devuelve un valor booleano:
Código

BOOL GetMessage( 
  LPMSG lpMsg, //Puntero a la estructura que contendrá el mensaje
  HWND hWnd, //Manejador de ventana, si es nulo, recibirá mensajes de cualquier ventana
             //que pertenezca al mismo proceso de la aplicación.

  UINT wMsgFilterMin, //Primer mensaje
  UINT wMsgFilterMax //Último mensaje
);


Solo falta el código que va dentro del bucle, que son un par de funciones que se encargan de traducir el mensaje, y enviar el mensaje a la función WindowsProc().

TranslateMessage() y DispatchMessage()
Ambas funciones solo reciben como parametro la dirección de memoria donde se encuentra el mensaje.
La primera se encarga de traducir los mensajes de teclas    virtuales a mensajes de carácter, es decir, traduce los mensajes de el código que envia el hardware a el código que pueda entender Windows.
La segunda envía el mensaje al procedimiento de    ventana, donde será tratado adecuadamente.

Estructura de un mensaje

Hemos hablado de los dichosos mensajes durante todo este texto, pero ¿que son en realidad? Son un tipo especial de variable especificado por windows en una estructura de la siguiente manera:
Código

struct MSG
{
HWND hwnd; //Manejador de la ventana que envia el mensaje
UINT message; // Identificador del mensaje
WPARAM wParam; // Parametro del mensaje (32 bits sin signo)
LPARAM lParam; // Parametro del mensaje (32 bits con signo)
DWORD time; // Tiempo que durará el mensaje en la cola
POINT pt; // Posición del mouse
}

No es necesario ajustar todas sus propiedades, bastará con solo asignar el parametro retornado al sistema operativo, o algunos más en otros tipos de mensajes mas avanzados.
El código del paso 4, quedará de la siguiente manera:



PROCESANDO LOS MENSAJES

Ya creamos la clase de ventana, creamos una ventana, asignamos la clase a la ventana, mostramos la ventana en pantalla y mandamos los mensajes a la funcion que los procesa. Pero esa función esta vacía, solamente tenemos que manejar los mensajes y ejecutar cierto código dependiendo del tipo de mensaje que sea.

La función DispatchMessage() le pasa el identificador del mensaje a WindowsProc().

El proceso para decodificar y tratar los mensajes recibidos, es con la sentencia de selección multiple switch() propia de C++.

Hay muchos tipos de mensajes diferentes, generalmente identificados con un prefijo seguido de un guión bajo y despues una palabra en ingles que identifica la acción del mensaje.
Los prefijos pueden ser:
BM_ Mensaje de un botón de comando
CB_ Mensaje de una lista desplegable
CDM_ Mensaje de un cuadro de dialogo común
DRV_ Mensaje de un Driver o controlador
EM_ Mensaje de Caja de Texto
LB_ Mensaje de ListBox
WM_ Mensaje de Ventana
Seguidos de palabras como
PAINT  -  Dibuja en la ventana
DESTROY  - Destruye la ventana
QUIT  -  Ocure al cerrar la ventana
LBUTTONDOWN   -   Ocurre al presionar el click izquierdo
LLBUTTONUP   -   Ocurre al soltar el click izquierdo

Para continuar nuestro ejemplo, vamos a procesar solo dos mensajes que van a ocurrir.
WM_PAINT: Ocurrirá al inicializar la ventana
WM_DESTROY: Ocurrirá al cerrar la ventana.

La función DefWindowProc() procesará los mensajes que no manejemos, ya que aunque no tengamos códdigo para ellos, de todas formas ocurren. Recibe los mismos parametros que WindowsProc(), hasta ahora nos debe quedar asi:


Empecemos por procesar el primer mensaje. WM_PAINT. Este mensaje sucede al crearse la ventana, asi que veremos su ejecución enseguida.

Vamos a 'pintar' un mensaje en la ventana. Para ello necesitaremos algunas variables y funciones nuevas que veremos a continuación. Lo que haré será colocar el código y despues lo explicaré:



Primero, creamos un dispositivo de contexto, que es la parte dibujable. Imaginemos que es como una hoja de papel que pegamos sobre la ventana.
Despues la estructura PAINTSTRUCT, que brinda la información desde la ventana al dispositivo de contexto. Es necesaria porque define el sector de memoria necesario para poder dibujar sobre el dispositivo de contexto.

BeginPaint()
Esta función recibe dos parametros, el identificador de la ventana, y la dirección de memoria de el PAINTSTRUCT. Esto devuelve un numero entero que se asigna como manejador al dispositivo de contexto. Con esto ya está todo preparado para comenzar a pintar cualquier cosa sobre la ventana.

Ahora, para definir que es lo que se va a dibujar, vamos a crear un rectangulo, que no es más que una simple estrcutrua  que define la parte exacta del dispositivo de contexto donde se va a dibujar.

GetClientRect()
Recupera las coordenadas del área de cliente de la   ventana. Las coordenadas de cliente especifican las esquinas superior  izquierda e   inferior derecha del área de cliente. Como las coordenadas de cliente  son relativas   a la esquina superior izquierda del área de cliente de la venana, las  coordenadas de   la esquina superior izquierda son (0,0).
Los unicos dos parametros que recibe son el manejador de la ventana y la dirección de memoria del rectangulo.

DrawText()
Ahora realmente dibujamos el texto usando DrawText( ), pasamos el HDC a usar y el string a dibujar. El tercer parámetro es la longitud del string, pero hemos pasado -1 debido a que DrawText( ) es lo suficientemente astuto para darse cuenta cual es la longitud del texto. En el cuarto parámetro pasamos la dirección de memoria del rectangulo donde estamos dibujando

EndPaint()
Marca el final del pintado para una ventana dada. La llamada a ésta función se requiere para cada llamada a la función BeginPaint(), pero sólo después de que el proceso de pintado se haya completo.
Recibe como parametros el manejador de la ventana y la dirección de memora del PAINTSTRUCT usado.

Por ultimo, y para terminar nuestra ventana, vamos a procesar el mensaje WM_DESTROY, que ocurre al cerrar la ventana. En este caso, queremos que al cerrar la ventana termine la ejecución del programa.
Para eso, solamente necesitaremos una función que nos proporciona la api de windows:

PostQuitMessage()
Envia un mensaje WM_QUIT a la cola de mensajes, el cual es procesado por DefWindowProc() para terminar la aplicación. Al mandarle un 0 como parametro indicamos que todo ha salido bien e indica una salida exitosa del programa.
Posterior a eso retornamos tambien un 0 al programa para que termine con exito.



Listo, tenemos nuestra primer ventana en C++ con la ayuda de la API de windows. Podemos ejecutar el programa sin problemas y tendremos nuestra bonita ventana saludando al mundo:




El codigo completo del programa quedaria asi:


#include <windows.h>
 
LRESULT CALLBACK WindowsProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch(msg)                //Analizamos el mensaje que recibe como parametro la función
   {
       case WM_PAINT:         //En caso que ocurra WM_PAINT
           HDC hDC; //Creamos un dispositivo de contexto (para poder dibujar)
           //Contiene información que puede ser usada para pintar el área de cliente de una ventana por la propia aplicación.
           PAINTSTRUCT ps;
           hDC = BeginPaint(hwnd, &ps);  //Prepara la ventana para dibujar sobre ella
           RECT  rectangulo;      //Creamos un rectangulo en la ventana para dibujar sobre el
           GetClientRect(hwnd, &rectangulo);   //Agregamos el rectangulo a la ventana
           //Función que dibuja un texto sobre el rectangulo creado
           DrawText(hDC,       //Identificador del dispositivo de context
                    "Hola Mundo!!!!!",//Texto a dibujar en pantalla
                    -1,              //Dejamos que la función ajuste automaticamente el tamaño del texto.
                    &rectangulo,     //Dirección del rectangulo donde se dibujará el texto
                    DT_SINGLELINE | DT_CENTER | DT_VCENTER
                    );
           EndPaint(hwnd, &ps);        //Termina el proceso de dibujo en la ventana
       break;
 
       case WM_DESTROY:          //En caso que ocurra WM_DESTROY
           PostQuitMessage(0);  //Ocurre el mensaje WM_QUIT para terminar la aplicación
           return 0;
       break;
   }
 
   //Esta función procesa los mensajes no tratados por WindowsProc(), como redimensionar
   //la ventana, maximizarla, etc.. , es decir, hace lo predterminado sin alterar su función
   return DefWindowProc(hwnd, msg, wParam, lParam);
}
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
   //1.-Especificar a Windows que tipo de ventana se requiere
   WNDCLASSEX miClase;   //Creamos la variable miClase tipo WNDCLASSEX
 
   miClase.cbSize = sizeof(WNDCLASSEX);//Tamaño de la clase es igual al tamaño de la estructura
   miClase.style = CS_HREDRAW | CS_VREDRAW;//Redibuja la ventana si cambia su tamaño
   miClase.lpfnWndProc = WindowsProc; //Define la función que procesa los mensajes
   miClase.cbClsExtra = 0;//No reservamos bytes extra despues de la clase
   miClase.cbWndExtra = 0;//No reservamos bytes extra despues de la instancia
   miClase.hInstance = hInstance;//Manejador de la instancia
   miClase.hIcon = LoadIcon(0, IDI_APPLICATION);//Ajusta el icono por default de un programa
   miClase.hCursor = LoadCursor(0, IDC_ARROW); //Ajusta el cursor por default de un programa
   miClase.hbrBackground = (HBRUSH)(COLOR_WINDOW); //El pincel ajusta el color de fondo
   miClase.lpszMenuName = 0;  //No tiene barra de menú
   miClase.lpszClassName = "claseVentana";  //Ajusta el nombre de la clase
   miClase.hIconSm = 0;  //Ajusta el icono pequeño igual que el icono de la aplicación
 
   RegisterClassEx(&miClase);  //Registra la clase de ventana para poder ser usada...
 
   //2.-Crear la ventana
   HWND hWnd;  //Variable tipo manejador de ventana,
                //donde se guardara el manejador de la ventana creada.

 
   hWnd = CreateWindow("claseVentana",     //Nombre de la clase utilizada para esta ventana
                       "Ventana creada en C++ con la API de Windows", //Titulo de la ventana
                       WS_OVERLAPPEDWINDOW,//Estilo de la ventana
                       300,                //Posición Horizontal en pixeles
                       300,               //Posición vertical en pixeles
                       640,               //Ancho de la ventana en pixeles
                       480,              //Alto de la ventana en pixeles
                       0,                //Sin ventana padre
                       0,                //Sin menú
                       hInstance,        //Instancia actual de la aplicación
                       0);              //Sin datos extra para la creación de la ventana
 
   ShowWindow(hWnd, nCmdShow);   //Muestra la ventana en pantalla
 
   //3.-Inicializar la ventana
 
   UpdateWindow(hWnd);
 
   //4.-Manejar los mensajes (se envian a WindowsProc() conforme vaya siendo necesario
   MSG mensaje;
   while(GetMessage(&mensaje,0,0,0) == TRUE)     //Obtiene cualquier mensaje
   {
       TranslateMessage(&mensaje);               //Traduce el mensaje
       DispatchMessage(&mensaje);                //Envia el mensaje a WindowsProc()
   }
 
   return mensaje.wParam;                      //Fin, regresa el control a Windows
 
}

Espero esto les ayude a entender como maneja Windows las ventanas. Al principio talvez parezca un poco complicado, pero con práctica se volverá mucho más facil. Aun faltan varias cosas, pero comprendiendo esto, lo demas será mucho más facil.

Autor: rob1104

Saludos a todos
Comments