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
D Ejemplo 4 win004.zip 2004-05-17 2901 bytes 1737

Comentarios de los usuarios (10)

Bandani
2010-12-06 08:58:05

En el ejemplo win004 al fichero .c se debe agregar la cabecera stdio (#include <stdio.h>) y reemplazar la linea nro.76 [static veces;] por [static int veces;]

david tadeo
2011-01-22 15:40:03

yo no necesite incluir stdio, lo mas probable es que en windows.h venga incluido stdio.h y el int tampoco por que es por defecto el queme da el compilador ( int ), uso dev-cpp, saludos a todos

Hugo
2012-04-09 12:04:16

Hola, estoy siguiendo este curso (buenisimo por cierto) estoy con devcpp y efectivamente si no incluyo stdio.h ni declaro como int la variable veces no compila. Y aun compilando no me funciona, lo curioso es que en el ejemplo funciona a las mil maravillas. ¿Alguna sugerencia?

Salvador Pozo
2012-04-09 15:34:54

Hola:

Efectivamente, el ejemplo tiene algunos errores (que el compilador emite como warnings). El primero es que la variable 'veces' es de tipo int, aunque en C se puede omitir el tipo cuando es int, no se considera una buena práctica, y en este caso falta por un error mio.

El segundo es que hay que incluir el fichero de cabecera "stdio.h", ya que el prototipo de la función "sprintf" se declara en él.

El tercero es que el tercer parámetro en la llamada a "sprintf" debe ser de tipo entero, y no LPARAM. Aunque se realiza una conversión interna, no está de más indicarlo claramente:

sprintf(texto, "Veces invocado: %d", (int)lParam);

Lo de que a veces compile o no, creo que es un problema de Dev-C++, ya que a veces no compila todos los módulos del proyecto. Suele fallar más cuando se modifican los ficheros de recursos.

Recomiendo encarecidamente cambiar a otro IDE, como Code::Blocks. Dev-C++ es un proyecto abandonado, y nunca pasó de la fase beta.

En una próxima revisión del curso repasaré todos los ejemplos. Me temo que hay varios errores que no siempre se muestran usando Dev-C++.

Hasta pronto

josé
2012-05-29 01:07:37

en el ejemplo 4 me tira error en setbkcolor() y textout()

busque y creo que pertenecen fichero cabecera graphics.h pero al incluirla me da error ....

es en realidad graphics.h el que falta o qué? señalar que estoy usando el compilador MinGW e ID eclipse

ups
2012-05-29 01:10:58

en el ejemplo 36 me tira error en setbkcolor() y textout()

busque y creo que pertenecen fichero cabecera graphics.h pero al incluirla me da error ....

es en realidad graphics.h el que falta o qué? señalar que estoy usando el compilador MinGW e ID eclipse

Steven R. Davidson
2012-05-29 01:40:59

Hola José,

Ambas funciones pertenecen al API de MS-Windows desde MS-Windows 95 y por tanto están incluidas en <windows.h> al incluir <wingdi.h>. Puedes consultar estos dos enlaces para ver sus descripciones: http://winapi.conclase.net/curso/?winfun=SetBkColor#inicio y http://winapi.conclase.net/curso/?winfun=TextOut#inicio

La función 'setbkcolor()' ciertamente aparece en la BGI (Interfaz Gráfica de Borland) para MS-DOS, pero no tiene vínculo con la función del API de MS-Windows que es 'SetBkColor()'. La función 'TextOut' es del API de MS-Windows, mientras que 'outtext()' es de la BGI. Puedes comprobar las funciones declaradas en <graphics.h> de la BGI yendo a: http://c.conclase.net/borland/index.php?borlandlib=graphics#inicio

Espero que esto te oriente.

Steven

Pascual
2014-10-01 19:30:36

Muchas gracias por este curso. Muy bueno estoy aprendiendo cantidad. Un saludo.

Sebastián
2015-11-09 23:34:03

Hola, estoy iniciando con C++. Es muy interesante. Pero a medida que avanzo tengo dudas.

Lo que no comprendo son los Resources. En el ejemplo WIN0004 veo que se carga un win004.rc pero no hay acceso a el desde win004.c. Básicamente cuando se utiliza y como se accede?

Saludos.

Seba.

Steven R. Davidson
2015-11-10 19:19:25

Hola Sebastián,

Explicamos su uso en este capítulo al hablar de la macro, 'DialogBox()'. En el ejemplo, escribimos,

case CM_DIALOGO:
   DialogBox(hInstance, "DialogoPrueba", hwnd, DlgProc);
   break;

El segundo parámetro es el nombre del recurso que previamente definimos en "win004.rc"; es decir,

DialogoPrueba DIALOG 0, 0, 118, 48
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION
...

También puedes consultar la referencia de esta macro yendo a: http://winapi.conclase.net/curso/?winfun=DialogBox para una descripción de cada parámetro.

En general, usamos un fichero de recursos para describir o configurar un cuadro de diálogo y sus controles, al igual que otros recursos. De esta manera, podemos separar el diseño de la programación usando una descripción sencilla y fácil. Claro está, podemos hacer todo esto directamente en C programando cada cuadro de diálogo, control, y recursos usando las funciones apropiadas. Veremos cómo hacer estas cosas dinámicamente a partir del capítulo 37.

Espero que esto aclare la duda.

Steven