Inicio Reflexiones Guitarra Engineering Programación Win32

Ventanas
Capítulo 3, por Ricardo González Garza


Este capítulo supone que tienes conocimientos previos en. . .
Manejo del entorno de desarrollo
- Como Dev-Cpp, VC.NET, etc.
C++
- ¿Qué es... ? struct, typedef
Haber leído el Capítulo 2
- ¿Qué es... ? WinMain(), MessageBox()

Tiempo estimado de lectura y práctica: 45 min.

3.1 Preparativos para un buen inicio

Crear una aplicación en Windows puede parecer complicado, ¡y vaya que lo es al principio! Las codificaciones te parecerán interminables y confusas, se topará con tipos de datos disfrazados (UINT en vez de unsigned int), términos incomprensibles y un sin fin de funciones nuevas... pero nada será en vano, debe pasar por aquí para disfrutar las ventajas de utilizar la API de Windows.

Cuando consiga ganar experiencia, se dará cuenta de que lo complicado no es mas que una mera apariencia, irá reconociendo funciones y sabrá qué usar y dónde buscar para realizar lo que tiene en mente. Con el tiempo, irá creando sus propias librerías para facilitar programar cosas cotidianas (como crear una ventana en una línea). La experiencia se gana superando esa prueba mental que impone la programación en Windows.

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

Sé que ahora tiene mucha duda sobre conceptos, tipos de datos, constantes... todo un mundo de información que necesito mostrarle en tan poco tiempo; no se preocupe, a medida que avance en el curso vendrá resolviéndolas. Por ahora solo me enfoco en las bases, en lo principal, lo realmente útil; los detalles vendrán en su momento.

Bueno, voy a hacer costumbre esto de iniciar con un código. Para esto, debe seguir 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) al que llamará Ejercicio2. Es importante hacerlo correctamente ya que si olvida indicar que se trata de un proyecto Windows, no logrará compilar el programa.
  3. Agregar un Archivo C++ al proyecto (ver apéndice B.2) que se llamará 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 transcribe cuidadosamente.
Programa2.cppCopiar cógido
#include <windows.h>

/* 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 se ocupa para 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 fue 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) > 0)
    {
        DispatchMessage(&mensaje);
    }

    return mensaje.wParam;
}
  1. Generar el proyecto (ver apéndice B.3) y esperar 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 le 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). En capítulos posteriores verá cómo crear sus propias librerías que facilitarán las cosas (estilo MFC o .NET).

3.2 Tipos de datos en Windows

Como seguramente lo habrá notado en el código anterior, las funciones API de Windows no hacen uso de los tipos de datos estándar en C++ (int, unsigned int, double, char*, etc.) Esto se hace por cuestiones de compatibilidad. Un manipulador (handle) deberá ser un entero de 32 bits sin depender del compilador en cuestión. Así, para cualquier cambio u actualización del compilador, solo basta modificar las librerías window.h (típicamente se entrega ya modificada junto con el compilador) para que el código continúe siendo compatible. Programar de esta manera resulta muy práctico y ahorra tiempo al programador.

Los tipos de datos más comúnes son:

Tipo de dato Declaración en C++ Descripción
UINT typedef unsigned int UINT; Valor entero sin signo de 4 bytes de tamaño (32 bits). De 0 a 4,294,967,295
BYTE typedef unsigned char BYTE; Caracter sin signo de 1 byte de tamño (8 bits). De 0 a 255.
DWORD typedef unsigned long DWORD; Valor de 4 bytes de tamaño (32 bits). Dependiendo del compilador, long puede ser igual o mayor que int. En mi compilador resultan iguales.
LONG typedef long LONG; Igual que el anterior, pero pudiendo representar enteros negativos.
BOOL typedef int BOOL; Número entero con signo (pueden existir números negativos).
CHAR typedef char CHAR; Lo mismo que char, valor de 1 byte de tamaño.
FAR #define FAR far
#define far
Se tiene por cuesitones de compatibilidad.
LPSTR typedef CHAR FAR * LPSTR; Puntero a una cadena de caracteres terminada en cero.
LPCSTR typedef const CHAR FAR * LPCSTR; Igual que el anterior pero como puntero constante.

Como puede ver no hay nada de que asustarse o que no conozca. En la programación Windows también es muy común lidiar con manipuladores, que no son más que simples números que sirven para identifican y referirse a ciertos elementos. Un manipulador se define como:

Definición de un manipuladorCopiar cógido
  #define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name;

  DECLARE_HANDLE(HINSTANCE);
  DECLARE_HANDLE(HWND);
  DECLARE_HANDLE...

Tipo de dato Declaración en C++ Descripción
HINSTANCE DECLARE_HANDLE(HINSTANCE); Manipulador de instancia. Es un puntero a una estructura que contiene un campo entero. Este, es un valor de 32 bits que identifica a la instancia abierta; es decir, Windows asigna un número para poder identificar al programa en ejecución y referirse a él con este número cuando sea necesario.
HWND DECLARE_HANDLE(HWND) Manipulador de ventana. Puntero a una estructura que contiene un campo entero. Este, es un valor de 32 bits que identifica a una ventana. Cada que sea necesario referirse a esta ventana en la funciones API, solo basta indicar este puntero.
HBRUSH DECLARE_HANDLE(HBRUSH) Manipulador de brocha para pintar algún área en pantalla.
HICON DECLARE_HANDLE(HICON) Manipulador para identificar a un ícono.

La lista de manipuladores puede continuar. Lo importante es conocer su existencia y su uso. Todos los tipos de manipuladores empiezan con H. Por medio de las funciones API es que se establecen los valores de los manipualdores según Windows asigne los recursos en cada sistema.

Adicionalmente, Windows define estrucuras como WNDCLASSEX o MSG las cuales abordaremos más adelante.

3.3 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.

Existen varias funciones API que en visitan la cola de mensajes (DispatchMessage()) y que regresan los eventos ocurridos que corresponden a la aplicación; con esto, se puede 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.

He introducido este 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. Pueda que el Programa2 aparente no hacer nada, pero en realidad se encuentra monitoreando la cola de mensajes con un ciclo while, y verificando cuando ocurra el evento de cierre de ventana, es decir, cuando el usuario da un clic sobre el botón cerrar o presiona Alt + F4. ¿Por qué monitorear ese evento? Porque cuando el usuario decide cerrar la ventana, se debe finalizar la aplicación.

Nota aclaratoria: Verá, cerrar una ventana y finalizar la aplicación son dos cosas completamente distintas. En Programa2 se presentan al mismo tiempo (cuando se cierra una ventana se finaliza la aplicación), pero ello no implica que deba ser así. Imagine que se tiene un documento el cual no ha sido guardado, el programa puede monitorear cuando el usuario desee cerrar la ventana y mostarle al usuario un mensaje "¡Si cierra la ventana perderá la información! ¿guardar cambios?". Calma, ¡ya tendremos tiempo para aprenderlo!

3.4 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? La explicación pudiera ser complicada, pero ponerlo en práctica no lo es. Ya sabe algunas cosas importantes: deberá incluir el archivo de cabecera windows.h y utilizar la función WinMain() que es el punto de partida de todo programa Windows. Por razones obvias, podrá imaginará que generalmente en el punto de partida es donde se decide crear una ventana.

Ahora bien, para crear una ventana hay que seguir 4 pasos. Primero diré cuáles son y en qué consisten para después 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 campos correspondientes a la estructura clase de ventana (la palabra clase no tiene nada que ver con la programación orientada a objetos). Verá, una clase de ventana almacena atributos que definen el aspecto y comportamiento de una ventana. Algunos ejemplos sobre la información que contiene son el color de fondo de la ventana, ícono, menú y el puntero de la función que será encargada de procesar los mensajes de la ventana. Examinando los archivos de cabecera de windows.h puede encontrar su declaración:

Declaración de WNDCLASSEXCopiar cógido
  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;

Observe como se declaran los campos con tipos de datos que no existen en C++ estándar (UINT, WNDPROC, HANDLE, HICON...), no le tome mucha importancia. Ya se ha mencionado que en algún punto de windows.h deben estar declarados. Como ejemplo, UINT corresponde a unsigned int o WNDPROC a un puntero de función. Los tipos de datos que comienzan con H, como HICON, HCURSOR, HBRUSH, se refieren a manipuladores (handlers) que no son más que punteros hacia un número entero que identifica ya sea al ícono, al cursor o a la brocha respectivamente.

Por ahora solo hay que concentrarse en los campos involucrados ya que almacenarán información reelevante para la creación de una ventana (o varias de ellas). Por medio de la notación húngara vamos a deducir lo siguiente:

Identificador Significado
cbSize contador de bytes de tamaño
style estilo
lpfnWndProc puntero "largo" a una función de procedimiento de ventana
cbClsExtra contador de bytes adicionales de la clase
cbWndExtra contador de bytes adicionales de la ventana
hInstance manipulador de instancia
hIcon manipulador del ícono
hCursor manipulador del cursor
hbrBackground manipulador de la brocha que pintará el 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 puede ver, la finalidad de algunos campos es muy descriptiva con solo leer su nombre (¡la ventaja de utilizar la notación húngara!), en otros quizás alcance a tener una vaga idea del significado porque no nunca ha utilizado esta estructura. Le introduciré al uso de cada uno de ellos en su momento. Mientras tanto, le mostraré una sección de código del Programa2 para observar cómo se precisa esta "configuración" de la ventana. Le advierto que aparecen nuevas funciones, constantes y, por si fuera poco, lo que parecieran tipos de datos que no corresponden al C++ estándar. No se preocupe por entender cada línea, solamente intento darle una vista muy general:

Establecer los 12 campos de la clase de ventanaCopiar cógido
    //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 por ahora son lpfnWndProc que apunta hacia la función encargada de recibir y manejar los eventos de la ventana (clics, pulsaciones, movimientos, cierre de ventana, 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, puede 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. A este campo bien pudo habérsele asignado el valor NULL para este ejemplo, pero no fue así intencionalmente para que observará el uso del operador OR |.

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

Dar de alta en el sistema la clase de ventanaCopiar cógido
    //2. Dar de alta en el sistema a la "clase de ventana"
    if( !RegisterClassEx(&ventana) )
    {
      MessageBox(NULL,
                 "No fue 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 del paso 1) en el sistema. Una vez registrada la clase de ventana, puede crear tantas ventanas como quiera las cuales tendrán el mismo ícono, color, menú, etc.

Cuando las configuraciones no son aceptadas por el sistema, la función regresa el valor NULL o 0 y significa que no fue posible dar de alta a la clase de ventana. Pudiese deberse a falta de recursos del sistema, aunque generalmente sucede cuando ha llenado incorrectamente alguno de los campos de la clase de ventana. De obtener el valor cero al utilizar esta función, deberá finalizar la aplicación o podría tener complicaciones. Para finalizar la aplicación se utiliza simplemente un return FALSE que obliga a salir de la función WinMain(). Recuerde que una aplicación termina cuando termina la función WinMain(). Observe que antes de que finalice la aplicación debido al return, por cortesía se ha decidido presentar al usuario un mensaje que explique lo sucedido utilizando la función MessageBox() vista en el capítulo anterior).

3. Crear la ventana en base a la clase de ventana anteriormente registrada Se manda llamar a la función CreateWindowEx() la cual cuenta con 12 parámetros. El pedazo de código:

Crear la ventana en base a la clase de ventanaCopiar cógido
    //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, ya que contiene el nombre de la clase de ventana a la cual pertenece. Esta debe coincidir con la cadena del campo lpszClassName de la clase de ventana indicada en el paso 1.

Si el nombre de la clase de ventana no existe o no ha sido aceptada por el sistema, no se permitirá crear la ventana y en ese caso la función devuelve el valor NULL. Por ello, no es de extrañarse que después de llamar a la función el código tenga una condición que compruebe si el valor regresado por CreateWindowEx() es igual a 0 (hwnd == NULL), y de ser el caso, muestra un mensaje de error y finaliza la aplicación con un return FALSE (no tiene caso seguir ejecutándola).

Y por último, 4. Mostrar la ventana:

Mostramos la ventanaCopiar cógido
    //4. Mostrar la ventana en pantalla
    ShowWindow(hwnd, SW_SHOWDEFAULT);

La función ShowWindow() es necesaria para mostrar la ventana como activa en pantalla. El manipulador de ventana hwnd contiene un puntero a una estructura que identifica a la ventana recién creada por CreateWindowEx(). No es importante saber cómo es que Windows asigna el puntero internamente, simplemente basta pensar que el sistema asigna un número único para identificar a la ventana en cuestión. Cada ventana tiene un "número único" o manipulador. En este caso, el manipulador de ventana hwnd sirve como primer parámetro de la función ShowWindow() para indicar la ventana que se mostrará en pantalla.

3.5 Cambiar el color de fondo de la ventana

El objetivo de este apartado es que interactúe con el código y adquiera confianza. No se preocupe por entrar en detalles que por ahora no necesita saber, los cómos y los porqués vendrán después. Hasta el momento, ya ha 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 "Panel de Control / Propiedades de pantalla / Apariencia".

Como ya sabe, en el paso 1 se definieron 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á sospechando, para cambiar el color del fondo de la ventana se tendrá que modificar el contenido de ese campo.

En el Programa2, la clase de ventana se declara con el identificador ventana. Localice la línea que dice "ventana.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);" y modifíquela por solo una de las siguientes líneas:

Formas de cambiar el color de ventanaCopiar cógido
//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);

   
/* Puede probar con estas otras constantes:
         COLOR_ACTIVEBORDER       COLOR_ACTIVECAPTION     COLOR_BACKGROUND
         COLOR_BTNSHADOW          COLOR_CAPTIONTEXT       COLOR_GRAYTEXT
         COLOR_HIGHLIGHTTEXT      COLOR_INACTIVEBORDER    COLOR_INACTIVECAPTION
         COLOR_MENUTEXT           COLOR_SCROLLBAR          COLOR_WINDOW
         COLOR_WINDOWFRAME        COLOR_WINDOWTEXT */


    //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)   donde los valores para rojo, verde y azul
    //tienen un rango de 0x00 a 0xFF
    ventana.hbrBackground = CreateSolidBrush( RGB(0xFF, 0xFF, 0xFF) ); //blanco
    ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0x00, 0x00) ); //negro
    ventana.hbrBackground = CreateSolidBrush( RGB(0xFF, 0x00, 0x00) ); //rojo
    ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0xFF, 0x00) ); //verde
    ventana.hbrBackground = CreateSolidBrush( RGB(0x00, 0x00, 0xFF) ); //azul
    ventana.hbrBackground = CreateSolidBrush( RGB(0xC0, 0xC0, 0xC0) ); //gris

      //No olvide borrar la brocha creada por CreateSolidBrush() antes de
      //finalizar la aplicación con la función DeleteObject(ventana.hbrBackground)

No creo que tenga problemas para cambiar la línea de código y observar el cambio en la ventana al compilar nuevamente. Si así lo desea, puede seguir practicando con cada una de las líneas anteriormente mostradas. Si utiliza la función CreateSolidBrush(), antes de finalizar la aplicación deberá borrar la brocha con DeleteObject(ventana.hbrBackground) para evitar desperdiciar los recursos del sistema.

3.6 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:

Prototipo de CreateWindowEx()Copiar cógido
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 lpWindowName, x, y, nWidth y nHeight. Cuide que lpWindowName sea una "cadena de caracteres terminada en cero" ya que se refiere al título que mostrará la ventana. También cuide que los parámetros del 5 al 8 sean números enteros. Modifique y vuelva a compilar para observar los cambios.

Como puede observar en el prototipo de la función, se regresa un tipo de dato HWND. Es el manipulador de ventana del que se ha estado hablando. Ese dato es muy útil para identificar en todo momento a la ventana que se crea. Ya sabemos que el manipulador devuelto 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).

3.7 Ejercicios

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

fig. 3.7.1 Así debe quedar la ventana del ejercicio 1.


comentarios@rickygzz.com.mx