Inicio Reflexiones Guitarra Engineering Programación Win32

Controles
Capítulo 6, por Ricardo González Garza


Este capítulo supone que tienes conocimeintos 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
- Saber generar y correr ejecutables en base a código C++

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

6.1 Controles de Windows

Existen una serie de controles que tienen la función de interactuar con el usuario. Un control es un tipo especial de ventana que permite un tipo específico de interacción del usuario. Por ahora nos concentraremos en 3 de los controles más utilizados en Windows: control etiqueta, control caja de texto y control botón.

fig. 6.1.1 Ventana generada con el Programa5.cpp

Antes de analizar líneas de código e introducirle en la teoría, me gustaría que viese el resultado de generar el Programa5.cpp ya que así le será más fácil imaginar el alcance de este capítulo. Para generar una ventana como la que se muestra en la figura anterior, es necesario seguir los siguientes 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 Ejercicio5.
  3. Agregar un Archivo de encabezado a nuestro proyecto (ver apéndice B.2) al que llamaremos ventana.h (todo en minúsculas). Este archivo lo reutilizaremos en otros proyectos para ahorrar líneas de código.
ventana.hCopiar cógido
#include <windows.h>

HWND CreaVentana(char szNombreAplicacion[], int ancho, int alto, HINSTANCE hInstancia, WNDPROC ProcedimientoVentana)
{
    HWND hwnd;

    WNDCLASSEX ventana;

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


   // Registrar la clase de ventana, si falla, salir del programa
   if(!RegisterClassEx(&ventana)) return (FALSE);

   hwnd = CreateWindowEx(
           0,
           szNombreAplicacion,     //Nombre de clase de la ventana a la que pertenece
           szNombreAplicacion,     //Título de la ventana
           WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT,
           CW_USEDEFAULT,
           ancho,                   //Ancho
           alto,                    //Alto
           HWND_DESKTOP,
           NULL,
           hInstancia,
           NULL);

   ShowWindow(hwnd, SW_SHOWNORMAL);

   return hwnd;
}
  1. Agregar un Archivo C++ a nuestro proyecto (ver apéndice B.2) al que llamaremos Programa5.cpp.
  2. Escribir el siguiente código en el archivo Programa5.cpp. Puede hacer uso del Copy&Paste para agilizar, más adelante analizaremos las partes del código correspondientes a la creación de controles.
Programa5.cppCopiar cógido
#include "ventana.h"

// Se encarga de informar si hubo un error al crear el control
// y cambia la fuente por la predeterminada en el Panel de Control
HWND Control(HWND hwndHija, HWND hwndPadre)
{
  if (hwndHija == NULL)
    MessageBox(hwndPadre, "No se puede crear el control.", "¡Error!",
               MB_OK | MB_ICONERROR);
  else
    SendMessage(hwndHija, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
                MAKELPARAM(FALSE, 0));

  return hwndHija;
}

// Crea una Etiqueta o Labelbox
//==============================
HWND Etiqueta(HWND hPadre, LPSTR texto, int x, int y, int ancho, int alto)
{
  HWND hEtiqueta;
  hEtiqueta = CreateWindowEx(NULL, "STATIC", texto, WS_CHILD | WS_VISIBLE,
                             x, y, ancho, alto, hPadre, NULL, NULL, NULL);

  return Control(hEtiqueta, hPadre);
}

// Crea una Caja de Texto o Textbox
//==================================
HWND Caja_Texto(HWND hPadre, LPSTR texto, int x, int y, int ancho, int alto)
{
  HWND hCaja_Texto;
  hCaja_Texto = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", texto,
                               WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
                               x, y, ancho, alto, hPadre, NULL, NULL, NULL);

  return Control(hCaja_Texto, hPadre);
}

// Crea un Botón
//===============
HWND Boton(HWND hPadre, LPSTR texto, int x, int y, int ancho, int alto)
{
  HWND hBoton;
  hBoton = CreateWindowEx(NULL, "BUTTON", texto,
                          WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
                          x, y, ancho, alto, hPadre, NULL, NULL, NULL);

  return Control(hBoton, hPadre);
}


HWND hETIQUETA, hCAJATEXTO, hBOTON;

LRESULT CALLBACK ProcedimientoVentana(HWND hPadre, UINT mensaje,
                                      WPARAM wParam, LPARAM lParam)
{
    switch (mensaje)
    {
       case WM_CREATE:
       {
          //Aquí deberán crearse los controles
          //==================================
          hETIQUETA = Etiqueta(hPadre,"Etiqueta", 10, 10, 180, 20);
          hCAJATEXTO = Caja_Texto(hPadre,"Caja Texto", 10, 40, 180, 20);
          hBOTON = Boton(hPadre,"Botón", 50, 70, 100, 30);

          break;
       }
       case WM_DESTROY:
       {
          PostQuitMessage (0);
          return 0;
       }
    }
    return DefWindowProc (hPadre, mensaje, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstancia, HINSTANCE hInstanciaPrev,
                   LPSTR lpLineaCmd, int nEstadoVentana)
{
   CreaVentana("Programa 5", 208, 150, hInstancia, ProcedimientoVentana);

   // Bucle de mensajes:
   MSG mensaje;
   while(GetMessage(&mensaje, 0, 0, 0) > 0)
   {
      TranslateMessage(&mensaje);
      DispatchMessage(&mensaje);
   }

   return (int) mensaje.wParam;
}
  1. Generar el proyecto (ver apéndice B.3) y esperar un momento mientras se genera el ejecutable.

¡Listo! Juege con la ventana. Quizás le parezcan muchas líneas para mostrar 1 ventana y 3 controles. Observe que puede maximizar la ventana, minimizarla, escribir en la caja de texto, seleccionar texto, copiar y pegar, el efecto sobre el botón de que se sumerge y vuelve a emerger al oprimirlo, etcétera. En realidad son unas cuantas líneas las necesarias para generar este programa si se toma en cuenta que pueden crearse funciones genéricas reutilizables en un archivo adicional (nuestra propia librería) tal como se hizo con ventana.h.

6.2 ¿Qué son los controles?

En realidad, para Windows, los controles no son más que ventanas que reciben y procesan ciertos mensajes de una u otra forma. Es por ello que para crear un control se utiliza la función que hemos estado utilizado para crear ventanas en el capítulo 3, CreateWindowEx(). Los controles generalmente son ventanas hijas (WS_CHILD) que pertenecen a una ventana padre (las ventanas que aprendió a hacer en el capítulo 3 son ventanas padre).

Para indicar que se trata de una ventana hija, se manda la constante WS_CHILD en el cuarto parámetro dwStyle de la función CreateWindowEx(). Los controles que se deseen crear como ventanas hijas, deberán crearse cuando la ventana padre reciba el mensaje WM_CREATE en el procedimiento de ventana.

Toda ventana tiene un Procedimiento de Ventana asociado. Windows proporciona una serie de clases de ventana las cuales implementan el comportamiento de los controles por medio de procedimientos de ventana por defecto DefWindowProc()

Código para crear controles al recibir el mensaje WM_CREATECopiar cógido
HWND hCONTROL;

LRESULT CALLBACK ProcedimientoVentana(HWND hPadre, UINT mensaje,
                                      WPARAM wParam, LPARAM lParam)
{
   switch (mensaje)
   {
       case WM_CREATE:
       {
          //Aquí deberán crearse los controles
          //==================================
          hCONTROL = CreateWindowEx(...);
          break;
       }
       case WM_DESTROY:
       {
          ...
       }
   }
   return DefWindowProc (hPadre, mensaje, wParam, lParam);
}

6.3 Control Etiqueta

El control etiqueta resulta de mucha utilidad para mostrar un texto al usuario sin que este pueda modificarlo. Para crear una etiqueta es necesario especificar la clase "STATIC" como segundo parámetro de la función CreateWindowEx(). Observe en el siguiente código que se deberá indicar WS_CHILD | WS_VISIBLE para que el control sea visible y sea una ventana hija de la ventana hPadre.

Crear un control EtiquetaCopiar cógido
  HWND hEtiqueta = CreateWindowEx(0, "STATIC", "Texto etiqueta",
                                  WS_CHILD | WS_VISIBLE,
                                  x, y, ancho, alto, hPadre, NULL, NULL, NULL);

Recuerde que la función CreateWindowEx() regresa el valor NULL en caso de no poder crear la ventana. Así que se deberá verificar el valor regresado e informar al usuario con un mensaje en caso de error. Si cuida estos pequeños detalles, su aplicación será más robusta.

fig. 6.3.1 Fuente de Windows 3.x y la configurada por defecto en instalación estándar de Win 9x/2000/XP

Se puede pensar que después de llamar a CreateWindowEx() se crea un control con la fuente configurada en el sistema dentro de Panel de Control / Pantalla / Apariencia, que sería Tahoma tamaño 8 en una instalación estándar. Sin embargo, por cuestiones de compatibilidad con aplicaciones desarrolladas para sistemas anteriores a Windows 95, muestra la fuente que fue utilizada en Windows 3.x (por ejemplo) la cual resulta antiestética en la mayoría de los casos. Sabiendo esto, es posible llamar a la función SendMessage() con WM_SETFONT para cambiar el tipo de fuente de nuestro control por la configurada en panel de control (DEFAULT_GUI_FONT) como se muestra en el siguiente código.

Función para crear un control EtiquetaCopiar cógido
HWND Etiqueta(HWND hPadre, LPSTR texto, int x, int y, int ancho, int alto)
{
  HWND hEtiqueta;

  //Añadir WS_BORDER para agregar un borde a la etiqueta
  hEtiqueta = CreateWindowEx(0, "STATIC", texto, WS_CHILD | WS_VISIBLE,
                             x, y, ancho, alto, hPadre, NULL, NULL, NULL);

  if (hEtiqueta == NULL)
  {
    MessageBox(hPadre, "No se puede crear etiqueta.", "¡Error!",
               MB_OK | MB_ICONERROR);
  }
  else
  { //Opcional: Ponemos la fuente configurada por defecto en el sistema
    SendMessage(hEtiqueta, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
                MAKELPARAM(FALSE, 0));
  }

  return hEtiqueta;
}

Modificando el primer y cuarto parámetro (dwExStyle y dwStyle respectivamente) de la función CreateWindowEx(), podemos cambiar la apriencia de la etiqueta. Tal como se muestra en la figura. Típicamente se utilizan los estilos de ventana de la etiqueta 1, 6 (WS_EX_CLIENTEDGE) y 8 (WS_EX_STATICEDGE).

fig. 6.3.2 Modificando los estilos de ventana dwExStyle y dwStyle
Códigos para generar las etiquetas de la ventana anteriorCopiar cógido
   //NOTA: falta verificar si se ha creado la ventana o de lo contrario mostrar un
   //      mensaje de error if (hEtiqueta == NULL) { mensaje... }
   //      Este código es meramente didáctico.

   HWND hETIQUETA = CreateWindowEx(NULL, "STATIC", "Etiqueta, fuente Windows 3.11",
                                   WS_CHILD | WS_VISIBLE, 10, 10, 180, 18,
                                   hPadre, NULL, NULL, NULL);

   HWND hETIQUETA1 = CreateWindowEx(NULL, "STATIC", "Etiqueta1",
                                    WS_CHILD | WS_VISIBLE,
                                    10, 40, 180, 18, hPadre, NULL, NULL, NULL);
   SendMessage(hETIQUETA1, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
               MAKELPARAM(FALSE, 0));

   HWND hETIQUETA2 = CreateWindowEx(NULL, "STATIC", "Etiqueta2 con WS_DISABLED",
                                    WS_CHILD | WS_VISIBLE | WS_DISABLED,
                                    10, 70, 180, 18, hPadre, NULL, NULL, NULL);
   SendMessage(hETIQUETA2, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
               MAKELPARAM(FALSE, 0));

   HWND hETIQUETA3 = CreateWindowEx(NULL, "STATIC", "Etiqueta3 con WS_BORDER",
                                    WS_CHILD | WS_VISIBLE | WS_BORDER,
                                    10, 100, 180, 18, hPadre, NULL, NULL, NULL);
   SendMessage(hETIQUETA3, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
               MAKELPARAM(FALSE, 0));

   HWND hETIQUETA4 = CreateWindowEx(NULL, "STATIC", "Etiqueta4 con WS_DLGFRAME",
                                    WS_CHILD | WS_VISIBLE | WS_DLGFRAME,
                                    10, 130, 180, 21, hPadre, NULL, NULL, NULL);
   SendMessage(hETIQUETA4, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
               MAKELPARAM(FALSE, 0));

   HWND hETIQUETA5 = CreateWindowEx(NULL, "STATIC", "Etiqueta5 con WS_THICKFRAME",
                                    WS_CHILD | WS_VISIBLE | WS_THICKFRAME,
                                    10, 160, 180, 21, hPadre, NULL, NULL, NULL);
   SendMessage(hETIQUETA5, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
               MAKELPARAM(FALSE, 0));

   HWND hETIQUETA6 = CreateWindowEx(WS_EX_CLIENTEDGE, "STATIC",
                                    "Etiqueta6 con WS_EX_CLIENTEDGE",
                                    WS_CHILD | WS_VISIBLE,
                                    210, 40, 230, 21, hPadre, NULL, NULL, NULL);
   SendMessage(hETIQUETA6, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
               MAKELPARAM(FALSE, 0));

   HWND hETIQUETA7 = CreateWindowEx(WS_EX_DLGMODALFRAME, "STATIC",
                                    "Etiqueta7 con WS_EX_DLGMODALFRAME",
                                    WS_CHILD | WS_VISIBLE,
                                    210, 70, 230, 21, hPadre, NULL, NULL, NULL);
   SendMessage(hETIQUETA7, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
               MAKELPARAM(FALSE, 0));

   HWND hETIQUETA8 = CreateWindowEx(WS_EX_STATICEDGE, "STATIC",
                                    "Etiqueta8 con WS_EX_STATICEDGE",
                                    WS_CHILD | WS_VISIBLE,
                                    210, 100, 230, 21, hPadre, NULL, NULL, NULL);
   SendMessage(hETIQUETA8, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT),
               MAKELPARAM(FALSE, 0));

6.4 Control Caja de texto

El control caja de texto permite al usuario introducir información a través del teclado. Para crear una caja de texto es necesario especificar la clase "EDIT" como segundo parámetro de la función CreateWindowEx(). Igual como sucede con la etiqueta, deberá indicarse WS_CHILD | WS_VISIBLE más la constante ES_AUTOHSCROLL que es deseable para la mayoría de los casos donde se desea que el usuario pueda seguir escribiendo aún si sobrepasa los límites del área dibujada del control. El 95% de los controles de caja de texto utilizan el estilo WS_EX_CLIENTEDGE para delimitar al control con un borde en 3D.

Copiar cógido
  HWND hCTexto = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
                                WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
                                x, y, ancho, alto, hPadre, NULL, NULL, NULL);

Es muy importante que la función TranslateMessage() se encuentre dentro del bucle de mensajes de la ventana padre, ya que genera los mensajes necesarios para permitir al usuario hacer uso del teclado para modifcar el texto de este control. En el capítulo anterior se omitía, ya que no era necesario adquirir datos del teclado.

De manera similar al control etiqueta, es posible modificar los parámetros dwExStyle y dwStyle de la función CreateWindowEx() para cambiar la apariencia de la caja de texto. Típicamente se utiliza el estilo del textbox 6 (WS_EX_CLIENTEDGE) y quizás del 9 (WS_EX_STATICEDGE).

fig. 6.4.1 Cajas de texto con diferentes estilos.

6.5 Control Botón

El control botón permite al usuario disparar acciones al oprimir sobre este con un clic. Pertenece a la clase "BUTTON". Igual que en los controles anteriores, deberá indicarse WS_CHILD | WS_VISIBLE. La clase "BUTTON" merece un capítulo adicional para explicar sus características a detalle.

Crear un control BotónCopiar cógido
  HWND hBoton = CreateWindowEx(NULL, "BUTTON", "",
                               WS_CHILD | WS_VISIBLE,
                               x, y, ancho, alto, hPadre, NULL, NULL, NULL);

6.6 TranslateMessage()

Típicamente se utiliza dentro del bucle de mensajes. La función TranslateMessage() debe incluirse si el programa hará uso del teclado. Convierte los mensajes de clave virtual a mensajes de caracteres. Esta función permite al usuario seleccionar opciones de un menú sin tener que utilizar el ratón (combinación ALT + tecla). Además, la función genera mensajes WM_CHAR que contribuyen a manipular pulsaciones del teclado.

Se acostumbra a incluirla ya que no entorpece la ejecución del programa y a menos que se trate de un programa muy simple, casi siempre es necesaria. En caso de que se requiera tener una caja de texto en el programa, se debe incluir por obligación.

Introducir la función TranslateMessage()Copiar cógido
   // Bucle de mensajes:
   MSG mensaje;
   while(GetMessage(&mensaje, 0, 0, 0) > 0)
   {
      TranslateMessage(&mensaje);
      DispatchMessage(&mensaje);
   }

6.7 Conclusión

Los controles permiten al usuario efectuar acciones sobre el programa para comunicar algún tipo de información. En el siguiente capítulo veremos como podemos crear nuestras propias librerías de código reutilizable, que harán que nuestros programas sean mucho más legibles y fáciles de codificar.



comentarios@rickygzz.com.mx