6 Diálogo básico

Los cuadros de diálogo son la forma de ventana más habitual de comunicación entre una aplicación Windows y el usuario. Para facilitar la tarea del usuario a la hora de introducir datos, existen varios tipos de controles, cada uno de ellos diseñado para un tipo específico de información. Los más comunes son los "static", "edit", "button", "listbox", "scroll", "combobox", "group", "checkbutton" y "ratiobutton". A partir de Windows 95 se indrodujeron varios controles nuevos: "updown", "listview", "treeview", "gauge", "tab" y "trackbar".

En realidad, un cuadro de diálogo es una ventana normal, aunque con algunas peculiaridades. También tiene su procedimiento de ventana (procedimiento de diálogo), pero puede devolver un valor a la ventana que lo invoque.

Igual que los menús, los cuadros de diálogo se pueden construir durante la ejecución o a partir de un fichero de recursos.

Ficheros de recursos

La mayoría de los compiladores de C/C++ que incluyen soporte para Windows poseen herramientas para la edición de recursos: menús, diálogos, bitmaps, etc. Sin embargo considero que es interesante que aprendamos a construir nuestros recursos con un editor de textos, cada compilador tiene sus propios editores de recursos, y no tendría sentido explicar cada uno de ellos. El compilador que usamos "Dev C++", en su versión 4, tiene un editor muy limitado y no aconsejo su uso. De hecho, en la versión actual ya no se incluye, y los ficheros de recursos se editan en modo texto.

De modo que aprenderemos a hacer cuadros de diálogo igual que hemos aprendido a hacer menús: usando el editor de texto.

Para el primer programa de ejemplo de programa con diálogos, que será el ejemplo 4, partiremos de nuevo del programa del ejemplo 1. Nuestro primer diálogo será muy sencillo: un simple cuadro con un texto y un botón de "Aceptar".

Este es el código del fichero de recursos:

#include <windows.h>
#include "win004.h"

Menu MENU
BEGIN
 POPUP "&Principal"
 BEGIN
  MENUITEM "&Diálogo", CM_DIALOGO
 END
END

DialogoPrueba DIALOG 0, 0, 118, 48
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION
CAPTION "Diálogo de prueba"
FONT 8, "Helv"
BEGIN
 CONTROL "Mensaje de prueba", TEXTO, "static",
    SS_LEFT | WS_CHILD | WS_VISIBLE,
    8, 9, 84, 8
 CONTROL "Aceptar", IDOK, "button",
    BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
    56, 26, 50, 14
END

Necesitamos incluir el fichero "windows.h" ya que en él se definen muchas constantes, como por ejemplo "IDOK" que es el identificador que se usa para el botón de "Aceptar".

También necesitaremos el fichero "win004.h", para definir los identificadores que usaremos en nuestro programa, por ejemplo el identificador del comando de menú para abrir nuestro diálogo.

/* Identificadores */

/* Identificadores de comandos */
#define CM_DIALOGO 101
#define TEXTO	   100

Lo primero que hemos definido es un menú para poder comunicarle a nuestra aplicación que queremos abrir un cuadro de diálogo.

A continuación está la definición del diálogo, que se compone de varias líneas. Puedes ver más detalles en el apartado de recursos dedicado al recurso diálogo.

De momento bastará con un identificador, como el que usábamos para los menús, y además las coordenadas y dimensiones del diálogo.

En cuanto a los estilos, las constantes para definir los estilos de ventana, que comienzan con "WS_", puedes verlos con detalle en la sección de constantes "estilos de ventana". Y los estilos de diálogos, que comienzan con "DS_", en "estilos de diálogo".

Para empezar, hemos definido los siguientes estilos:

  • DS_MODALFRAME: indica que se creará un cuadro de diálogo con un marco de dialog-box modal que puede combinarse con una barra de título y un menú de sistema.
  • WS_POPUP: crea una ventana "pop-up".
  • WS_VISIBLE: crea una ventana inicialmente visible.
  • WS_CAPTION: crea una ventana con una barra de título, (incluye el estilo WS_BORDER).

La siguiente línea es la de CAPTION, en ella especificaremos el texto que aparecerá en la barra de título del diálogo.

La línea de FONT sirve para especificar el tamaño y el tipo de fuente de caracteres que usará nuestro diálogo.

Después está la zona de controles, en nuestro ejemplo sólo hemos incluido un texto estático y un botón.

Un control estático (static) nos sirve para mostrar textos o rectángulos, que podemos usar para informar al usuario de algo, como etiquetas o como adorno. Para más detalles ver control static.

CONTROL "Mensaje de prueba", -1, "static",
   SS_LEFT | WS_CHILD | WS_VISIBLE,
   8, 9, 84, 8
  • CONTROL es una palabra clave que indica que vamos a definir un control.
  • A continuación, en el parámetro text, introducimos el texto que se mostrará.
  • id es el identificador del control. Como los controles static no se suelen manejar por las aplicaciones no necesitamos un identificador, así que ponemos -1.
  • class es la clase de control, en nuestro caso "static".
  • style es el estilo de control que queremos. En nuestro caso es una combinación de un estilo estático y varios de ventana:
    • SS_LEFT: indica un simple rectángulo y el texto suministrado se alinea en su interior a la izquierda.
    • WS_CHILD: crea el control como una ventana hija.
    • WS_VISIBLE: crea una ventana inicialmente visible.
  • coordenada x del control.
  • coordenada y del control.
  • width: anchura del control.
  • height: altura del control.

El control button nos sirve para comunicarnos con el diálogo, podemos darle comandos del mismo tipo que los que proporciona un menú. Para más detalles ver recurso button.

CONTROL "Aceptar", IDOK, "button",
   BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
   56, 26, 50, 14
  • CONTROL es una palabra clave que indica que vamos a definir un control.
  • A continuación, en el parámetro text, introducimos el texto que se mostrará en su interior.
  • id es el identificador del control. Nuestra aplicación recibirá este identificador junto con el mensaje WM_COMMAND cuando el usuario active el botón. La etiqueta IDOK está definida en el fichero Windows.h.
  • class es la clase de control, en nuestro caso "button".
  • style es el estilo de control que queremos. En nuestro caso es una combinación de varios estilos de button y varios de ventana:
    • BS_PUSHBUTTON: crea un botón corriente que envía un mensaje WM_COMMAND a su ventana padre cuando el usuario selecciona el botón.
    • BS_CENTER: centra el texto horizontalmente en el área del botón.
    • WS_CHILD: crea el control como una ventana hija.
    • WS_VISIBLE: crea una ventana inicialmente visible.
    • WS_TABSTOP: define un control que puede recibir el foco del teclado cuando el usuario pulsa la tecla TAB. Presionando la tecla TAB, el usuario mueve el foco del teclado al siguiente control con el estilo WS_TABSTOP.
  • coordenada x del control.
  • coordenada y del control.
  • width: anchura del control.
  • height: altura del control.

Procedimiento de diálogo

Como ya hemos dicho, un diálogo es básicamente una ventana, y al igual que aquella, necesita un procedimiento asociado que procese los mensajes que le sean enviados, en este caso, un procedimiento de diálogo.

Sintaxis

BOOL CALLBACK DialogProc(
    HWND hwndDlg,  // manipulador del cuadro de diálogo
    UINT uMsg,     // mensaje
    WPARAM wParam, // primer parámetro del mensaje
    LPARAM lParam  // segundo parámetro del mensaje
   );
  • hwndDlg identifica el cuadro de diálogo y es el manipulador de la ventana a la que está destinado el mensaje.
  • msg es el código del mensaje.
  • wParam es el parámetro de tipo palabra asociado al mensaje.
  • lParam es el parámetro de tipo doble palabra asociado al mensaje.

La diferencia con el procedimiento de ventana que ya hemos visto está en el tipo de valor de retorno, que es el caso del procedimiento de diálogo es de tipo booleano. Puedes consultar una sintaxis más completa de esta función en DialogProc.

Excepto en la respuesta al mensaje WM_INITDIALOG, el procedimiento de diálogo debe retornar con un valor no nulo si procesa el mensaje y cero si no lo hace. Cuando responde a un mensaje WM_INITDIALOG, el procedimiento debe retornar cero si llama a la función SetFocus para poner el foco a uno de los controles del cuadro de diálogo. En otro caso, debe retornar un valor distinto de cero, y el sistema pondrá el foco en el primer control del diálogo que pueda recibirlo.

Prototipo de procedimiento de diálogo

El prototipo es parecido al de los procedimientos de ventana:

BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

Implementación de procedimiento de diálogo para nuestro ejemplo

Nuestro ejemplo es muy sencillo, ya que nuestro diálogo sólo puede proporcionar un comando, así que sólo debemos responder a un tipo de mensaje WM_COMMAND y al mensaje WM_INITDIALOG.

Según hemos explicado un poco más arriba, del mensaje WM_INITDIALOG debemos retornar con un valor distinto de cero si no llamamos a SetFocus, como es nuestro caso.

Este mensaje lo usaremos para inicializar nuestro diálogo antes de que sea visible para el usuario, siempre que haya algo que inicializar, claro.

Cuando procesemos el mensaje WM_COMMAND, que será siempre el que procede del único botón del diálogo, cerraremos el diálogo llamando a la función EndDialog y retornaremos con un valor distinto de cero.

En cualquier otro caso retornamos con FALSE, ya que no estaremos procesando el mensaje.

Nuestra función queda así:

BOOL CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)                  /* manipulador del mensaje */
    {
        case WM_INITDIALOG:
           return TRUE;
        case WM_COMMAND:
           EndDialog(hDlg, FALSE);
           return TRUE;
    }
    return FALSE;
}

Bueno, sólo nos falta saber cómo creamos un cuadro de diálogo. Para ello usaremos un comando de menú, por lo tanto, el diálogo se activará desde el procedimiento de ventana.

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static HINSTANCE hInstance;

    switch (msg)                  /* manipulador del mensaje */
    {
        case WM_CREATE:
           hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
           return 0;
           break;
        case WM_COMMAND:
           switch(LOWORD(wParam)) {
              case CM_DIALOGO:
                 DialogBox(hInstance, "DialogoPrueba", hwnd, DlgProc);
                 break;
           }
           break;
        case WM_DESTROY:
           PostQuitMessage(0);    /* envía un mensaje WM_QUIT a la cola de mensajes */
           break;
        default:                  /* para los mensajes de los que no nos ocupamos */
           return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

En este procedimiento hay varias novedades:

Primero hemos declarado una variable estática "hInstance" para tener siempre a mano un manipulador de la instancia actual.

Para inicializar este valor hacemos uso del mensaje WM_CREATE, que se envía a una ventana cuando es creada, antes de que se visualice por primera vez. Aprovechamos el hecho de que nuestro procedimiento de ventana sólo recibe una vez este mensaje y de que lo hace antes de poder recibir ningún otro mensaje o comando. En el futuro veremos que se usa para toda clase de inicializaciones.

El mensaje WM_CREATE tiene como parámetro en lParam un puntero a una estructura CREATESTRUCT que contiene información sobre la ventana. En nuestro caso sólo nos interesa el campo hInstance.

La otra novedad es la llamada a la función DialogBox, que es la que crea el cuadro de diálogo.

Nota:

Bueno, en realidad DialogBox no es una función, sino una macro, pero dado su formato y el modo en que se usa, la consideraremos como una función.

Esta función necesita varios parámetros:

  1. Un manipulador a la instancia de la aplicación, que hemos obtenido al procesar el mensaje WM_CREATE.
  2. Un identificador de recurso de diálogo, este es el nombre que utilizamos para el diálogo al crear el recurso, entre comillas.
  3. Un manipulador a la ventana a la que pertenece el diálogo.
  4. Y la dirección del procedimiento de ventana que hará el tratamiento del diálogo.

Y ya tenemos nuestro primer ejemplo del uso de diálogos, en capítulos siguientes empezaremos a conocer más detenidamente cómo usar cada uno de los controles básicos: Edit, List Box, Scroll Bar, Static, Button, Combo Box, Group Box, Check Button y Radio Button. Le dedicaremos un capítulo a cada uno de ellos.

Pasar parámetros a un cuadro de diálogo

Tenemos otra opción a la hora de crear un diálogo. En lugar de usar la macro DialogBox, podemos usar la función DialogBoxParam, que nos permite enviar un parámetro extra al procedimiento de diálogo. Este parámetro se envía a través del parámetro lParam del procedimiento de diálogo, y puede contener un valor entero, o lo que es mucho más útil, un puntero.

Esta función tiene los mismos parámetros que DialogBox, más uno añadido. Este quinto parámetro es el que podemos usar para pasar y recibir valores desde el procedimiento de diálogo.

Por ejemplo, supongamos que queremos saber cuántas veces se ha invocado un diálogo. Para ello llevaremos la cuenta en el procedimiento de ventana, incrementando esa cuenta cada vez que recivamos un comando para mostrar el diálogo. Además, pasaremos ese valor como parámetro lParam al procedimiento de diálogo.

    static int veces;
...
        case WM_COMMAND:
           switch(LOWORD(wParam)) {
              case CM_DIALOGO:
                 DialogBox(hInstance, "DialogoPrueba", hwnd, DlgProc);
                 break;
              case CM_DIALOGO2:
                 veces++;
                 DialogBoxParam(hInstance, "DialogoPrueba2", hwnd, DlgProc2, veces);
                 break;
           }
           break;

Finalmente, nuestro procedimiento de diálogo tomará ese valor y lo usará para crear el texto de un control estático. (Cómo funciona esto lo veremos en otro capítulo, de momento sirva como ejemplo).

BOOL CALLBACK DlgProc2(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    char texto[25];

    switch (msg)                  /* manipulador del mensaje */
    {
        case WM_INITDIALOG:
           sprintf(texto, "Veces invocado: %d", lParam);
           SetWindowText(GetDlgItem(hDlg, TEXTO), texto);
           return TRUE;
        case WM_COMMAND:
           EndDialog(hDlg, FALSE);
           return TRUE;
    }
    return FALSE;
}

Hemos usado la función estándar sprintf para conseguir un texto estático a partir del parámetro lParam. Posteriormente, usamos ese texto para modificar el control estático TEXTO.

Usamos la misma plantilla de diálogo para ambos ejemplos, y aprovechamos el control estático para mostrar nuestro mensaje. La función SetWindowText se usa para cambiar el título de una ventana, pero también sirve para cambiar el texto de un control estático.

Cuando usemos cuadros de diálogo para pedir datos al usuario veremos que este modo de crearlos nos facilita en intercambio de datos entre la aplicación y los procedimientos de diálogo. De otro modo tendríamos que acudir a variables globales.

Ejemplo 4

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 4 win004.zip 2024-10-07 2907 bytes 996