[ contenidos | #winprog ]

Diálogos, los mejores amigos de los programadores de GUIs

Ejemplo: dlg_one

[images/dlg_one.gif]

Difícilmente exista un programa en Windows que no use cajas de diálogos. Simplemente haz click en File/Open de cualquier editor de textos o cualquier otro tipo de editor y ¡sorpresa! estás enfrente de una caja de diálogo, una que probablemente te permita escoger el archivo que deseas abrir.

Los diálogos no están limitados a los diálogos estandar para abrir un archivo, por el contrario, pueden lucir como se te ocurra. El punto atractivo de los diálogos es que proveen una forma rápida de ordenar y crear Interfaces Graficas al Usuario (GUI) y también realizar algunos procesos por default, disminuyendo la cantidad de código que debemos escribir.

Una cosa para recordar es que los diálogos son solo ventanas. La diferencia entre un dialogo y una ventana "normal" es que, con los diálogos, el sistema hace algunos procesos adicionales por default, como crear e inicializar controles y manipular el orden del tab. Todas las APIs que son aplicables a las ventanas "normales" funcionan correctamente en los diálogos y viceversa.

El primer paso es crear el recurso dialogo. La manera en que se hace depende de cada compilador/IDE, al igual que con todos los recursos. Aqui, voy a mostrarte el texto plano del dialogo en el archivo .rc y debes incorporarlo tu mismo en tu proyecto.

IDD_ABOUT DIALOG DISCARDABLE  0, 0, 239, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "My About Box"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "&OK",IDOK,174,18,50,14
    PUSHBUTTON      "&Cancel",IDCANCEL,174,35,50,14
    GROUPBOX        "About this program...",IDC_STATIC,7,7,225,52
    CTEXT           "An example program showing how to use Dialog Boxes\r\n\r\nby theForger",
                    IDC_STATIC,16,18,144,33
END

En la primer línea, IDD_ABOUTDLG es el ID del recurso. DIALOG es el tipo de recurso y los cuatro numeros son: coordenada X de la esquina superior Izquierda, coordenada Y de la esquina superior Izquierda, Ancho y Alto del dialogo. Estas NO SON PIXELES, están expresadas en unidades de diálogos que están basadas en el tamaño de la fuente usada por el sistema (y elegida por el usuario). Si tienes seleccionada una fuente grande el dialgo será grande y si utilizas una fuente pequeña el dialogo será mucho menor. Esto es importante porque asegura que todos los controles tengan el tamaño apropiado para poder mostrar adecuadamente el texto con la fuente seleccionada. Puedes convertir unidades de diálogo a pixeles en tiempo de ejecución usando MapDialogRect( ). DISCARDABLE le dice al sistema que puede realizar un "swap" para copiar el dialogo de la memoria al disco cuando no está siendo utilizado para conservar recursos del sistema.

La segunda linea comienza con STYLE y sigue con los estilos de ventana que serán usados para crear el diálogo. Esto debería estar explicado en CreateWindow() en tus archivos de ayuda. Para poder usar las constantes predefinidas necesitamos agregar #include "windows.h" en nuestro archivo de recursos .rc o en el caso de VC++ podemos usar "winres.h" o "afxres.h". Si utilizas un editor de recursos estos archivos serán incluídos automáticamente, si es necesario.

Con CAPTION indicamos el título del diálogo.

La línea FONT especifica el nombre y tamaño de la fuente que utilizaremos para este diálogo. Esta puede no ser la misma para todas las computadoras, depende de las fuentes que cada persona tenga instaladas y de los tamaños que haya especificado.

Ahora tenemos la lista de controles para crear el diálogo

    DEFPUSHBUTTON   "&OK",IDOK,174,18,50,14

Esta es la línea para el botón OK. El "&" indica que la letra que sigue (en este caso la "O") puede ser combinada con la tecla Alt para activcar el control mediante el teclado (Alt+O, en este caso). Todo esto es parte del proceso automático que he mensionado. IDOK es el identificador del control. IDOK es un control pre-definido por lo tanto no necesitamos definirlo. Los cuatro números al final son las coordenadas X e Y de la esquina superior izquierda y el ancho y alto de botón, todas en unidades de diálogos.

Si utilizas un editor de recursos para crear diálogos, esta información debería ser puramente académica, de todos modos suele ser necesario conocer como hacerlo directamente en el archivo de recursos (.rc), especialmente si no tienes un editor visual de recursos.

Hay dos controles que tienen un ID IDC_STATIC (el cual es -1), esto es usado para indicar que nunca necesitamos acceder a ellos, por lo tanto no necesitamos un identificador. Sin embargo esto no quita la posibilidad de darle un ID o que editor de recursos lo haga automáticamente.

El texto "\r\n" en la linea del CTEXT es el par CR-LF que representa el comienzo de una nueva linea.

Bien, habiendo agregado esto a nuestro archivo de recursos (.rc) necesitamos escribir un Dialog Procedure para procesar los mensajes de este dialogo. No te preocupes esto no es nada nuevo, prácticamente es lo mismo que con nuestro Window Procedure (aunque no exactamente).

BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
        case WM_INITDIALOG:

        return TRUE;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDOK:
                    EndDialog(hwnd, IDOK);
                break;
                case IDCANCEL:
                    EndDialog(hwnd, IDCANCEL);
                break;
            }
        break;
        default:
            return FALSE;
    }
    return TRUE;
}

Hay algunas diferencias importantes entre un Dialog Procedure y un Windows Procedure. Una es que NO llamamos a DefWindowProc( ) para mensajes que no manejamos. Con los diálogos esto es realizado automáticamente.

Otra diferencia es que, generalmente, retornamos FALSE para aquellos mensajes que no procesamos y TRUE para aquellos que si procesamos, A MENOS que el mensaje especifique que valor debemos retornar. Observa que esto es lo que hicimos anteriormente, por default, no debemos hacer nada y retornar FALSE, mientras que los mensajes que si procesamos entran en el switch() y retornan TRUE.

La siguiente diferencia es que no llamamos a DestroyWindow() para cerrar el diálogo, si no que utilizamos EndDialog( ). El segundo parámetro es el valor retornado a quien halla llamado a DialogBox().

Por último, en vez de procesar WM_CREATE, procesamos WM_INITDIALOG para realizar cualquier proceso que sea necesario antes que aparezca el diálogo y luego retornamos TRUE para darle el foco al teclado en el control asignado por default.(Puedes procesar el mensaje WM_CREATE, pero éste es enviado ANTES que los controles hayan sido creados, por lo tanto no puedes acceder a ellos. Cuando procesamos WM_INITDIALOG los controles ya han sido creados, lo que nos permite acceder a ellos).

Suficiente, hagámoslo...

case WM_COMMAND:
    switch(LOWORD(wParam))
    {
        case ID_HELP_ABOUT:
        {
            int ret = DialogBox(GetModuleHandle(NULL), 
                MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
            if(ret == IDOK){
                MessageBox(hwnd, "Dialog exited with IDOK.", "Notice",
                    MB_OK | MB_ICONINFORMATION);
            }
            else if(ret == IDCANCEL){
                MessageBox(hwnd, "Dialog exited with IDCANCEL.", "Notice",
                    MB_OK | MB_ICONINFORMATION);
            }
            else if(ret == -1){
                MessageBox(hwnd, "Dialog failed!", "Error",
                    MB_OK | MB_ICONINFORMATION);
            }
        }
        break;
        // Other menu commands...
    }
break;

Este es el código que he usado para crear mi dialogo "About", seguramente te preguntas por que está puesto dentro del manejador WM_COMMAND, si no tienes claro este aspecto, puedes revisar la sección de menués e iconos. ID_HELP_ABOUT es el dentificador del item menú Help\About.

Debido a que queremos que el menú de nuestra ventana pricipal cree el diálogo, obviamnete debemos poner este código en el WinProc( ) de nuestra ventana principal, no en el Dialog Procedure.

Ademas, he almacenado los valores de retorno de las llamadas a DialogBox( ), solamente con el propósito de observar el efecto de presionar dentro del diálogo los dos Botones, Esc, Enter, etc... y poder ilustrar como utilizar los valores de retorno de un diálogo para chequear si tubo éxito, hubo alguna falla o para ver la elección del usuario o alguna otra información que elijas para enviársela de retorno a quien lo llame desde el Dialog Procedure.

DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);

IDD_ABOUT es el ID del recurso diálogo. hwnd el el handle a la ventana padre del diálogo. AboutDlgProc( ) es, por su puesto, el Dialog Procedure usado para controlar el diálogo.

Un astuto lector eventualmente podría haberse preguntado: si DialogBox() no retorna hasta que el diálogo sea cerrado, entonces no podemos procesar mensajes mientras el dialogo esté activo, ¿como funciona esto?. Bueno, el tema es que los diálogos tienen su propio bucle de mensajes, por lo tanto mientras el diálogo esta siendo mostrado en pantalla (esta activo), nuestro bucle de mensajes es pasado a un segundo plano y empieza a ejecutarse el bucle que Windows utiliza por default. Este bucle es manejado por Windows y también se encarga de cosas como mover el foco del teclado de control en control cada vez que presionamos la tecla Tab.

Otro efecto de usar DialogBox( ) es que la ventana principal queda desactivada hasta que el diálogo sea despachado. A veces esto es lo que deseamos y otras veces no, como cuando queremos usar un diálogo con una barra de herramientas flotante. En este caso queremos ser capaz de interactuar con ambos, la ventana y el diálogo. Pero esto será visto en la próxima sección.


Copyright © 1998-2003, Brook Miles (theForger). All rights reserved.

Versión en español: Federico Pizarro - 2003