Capítulo 14 Control básico Checkbox

En realidad no se trata más que de otro estilo de botón.

Normalmente, los CheckBoxes pueden tomar dos valores, encendido y apagado. Aunque también exiten Checkbox de tres estados, en ese caso, el tercer estado corresponde al de inhibido. Los CheckBoxes se usan típicamente para leer opciones que sólo tienen dos posibilidades, del tipo cuya respuesta es sí o no, encendido o apagado, verdadero o falso, etc.

Aunque a menudo se agrupan, en realidad los checkboxes son independientes, cada uno suele tomar un valor de tipo booleano, independientemente de los valores del resto del grupo, si es que pertenece a uno.

El aspecto normal es el de una pequeña caja cuadrada con un texto a uno de los lados, normalmente a la derecha. Cuando está activo se muestra una marca en el interior de la caja, cuando no lo está, la caja aparece vacía. También es posible mostrar el checkbox como un botón corriente, en ese caso, al activarse se quedará pulsado.

Ficheros de recursos

Vamos a mostrar algunos de los posibles aspectos de los CheckBoxes:

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

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

DialogoPrueba DIALOG 0, 0, 168, 95
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "CheckBoxes"
FONT 8, "Helv"
BEGIN
 CONTROL "Normal", ID_NORMAL, "BUTTON", 
    BS_CHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    12, 4, 60, 12
 CONTROL "Auto", ID_AUTO, "BUTTON", 
    BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    84, 4, 72, 12
 CONTROL "Tres estados", ID_TRISTATE, "BUTTON", 
    BS_3STATE | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    12, 17, 60, 12
 CONTROL "Auto tres estados", ID_AUTOTRISTATE, "BUTTON", 
    BS_AUTO3STATE | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    84, 16, 76, 12
 CONTROL "Auto Push", ID_AUTOPUSH, "BUTTON", 
    BS_AUTOCHECKBOX | BS_PUSHLIKE | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    12, 32, 60, 12
 CONTROL "Auto Tristate Push", ID_AUTOTRIPUSH, "BUTTON", 
    BS_AUTO3STATE | BS_PUSHLIKE | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    83, 32, 75, 12
 CONTROL "Derecha", ID_DERECHA, "BUTTON", 
    BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    12, 50, 60, 12
 CONTROL "Plano", ID_PLANO, "BUTTON", 
    BS_AUTOCHECKBOX | BS_FLAT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    84, 50, 60, 12
 CONTROL "Aceptar", IDOK, "BUTTON", 
    BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    12, 69, 50, 14
 CONTROL "Cancelar", IDCANCEL, "BUTTON", 
    BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    84, 69, 50, 14
END

Para ver más detalles acerca de este tipo de controles ver controles button.

Como se puede observar, un CheckBox es un tipo de botón con uno de los siguientes estilos BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE o BS_AUTO3STATE.

Podemos dividir los CheckBoxes en cuatro categorías diferentes, dependiendo de si son o no automáticos o de si son de dos o tres estados.

 CONTROL "Normal", ID_NORMAL, "BUTTON", 
   BS_CHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
   12, 4, 60, 12
  • CONTROL es la palabra clave que indica que vamos a definir un control.
  • A continuación, en el parámetro text, en el caso de los CheckBox será el texto que aparecerá acompañando a la caja o en el interior del botón y que servirá para identificar el valor a editar.
  • id es el identificador del control. El identificador será necesario en algunos casos para procesar los comandos procedentes del CheckBox, en el caso de los no automáticos será la única forma de tratarlos.
  • 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 un estilo button y varios de ventana:
    • BS_CHECKBOX: Indica que se trata de un CheckBox de dos estados.
    • BS_AUTOCHECKBOX: Indica que se trata de un CheckBox de dos estados automático.
    • BS_3STATE: Indica que se trata de un CheckBox de tres estados.
    • BS_AUTO3STATE: Indica que se trata de un CheckBox de tres estados automático.
    • BS_PUSHLIKE: Indica que se trata de un CheckBox con la apariencia de un botón corriente.
    • BS_LEFTTEXT: Indica que el texto del CheckBox se sitúa a la izquierda de la caja.
    • BS_FLAT: Indica apariencia plana, sin las sombras que simulan 3D.
    • WS_CHILD: crea el control como una ventana hija.
    • WS_VISIBLE: crea una ventana inicialmente visible.
    • WS_TABSTOP: para que cuando el foco cambie de control al pulsar TAB, pase por este control.
  • coordenada x del control.
  • coordenada y del control.
  • width: anchura del control.
  • height: altura del control.

Iniciar controles CheckBox

Los controles CheckBox suelen precisar inicialización.

Para este ejemplo también usaremos variables globales para almacenar los valores de las variables que se pueden editar con los CheckBoxes.

// Datos de la aplicación
typedef struct stDatos {
   BOOL Normal;
   BOOL Auto;
   int TriState;
   int AutoTriState;
   BOOL AutoPush;
   int AutoTriPush;
   BOOL Derecha;
   BOOL Plano;
} DATOS;

Creamos una variable estática en nuestro procedimiento de ventana para almacenar los datos, y le daremos valores iniciales a las variables de la aplicación, al procesar el mensaje WM_CREATE:

    static DATOS Datos;
    static HINSTANCE hInstance;
...    
        case WM_CREATE:
           hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
           /* Inicialización */
           Datos.Normal = TRUE;
           Datos.Auto = TRUE;
           Datos.TriState = (int)FALSE;
           Datos.AutoTriState = (int)FALSE;
           Datos.AutoPush = FALSE;
           Datos.AutoTriPush = (int)TRUE;
           Datos.Derecha = TRUE;
           Datos.Plano = FALSE;
           return 0;

Al crear el cuadro de diálogo, usaremos la función DialogBoxParam, y en el parámetro lParam pasaremos el puntero a nuestra estructura de datos:

              DialogBoxParam(hInstance, "DialogoPrueba", hwnd, DlgProc, (LPARAM)&Datos);

Como siempre, para establecer los valores iniciales de los controles CheckBox usaremos el mensaje WM_INITDIALOG del procedimiento de diálogo.

Para eso usaremos la función CheckDlgButton o el mensaje BM_SETCHECK, en este último caso, emplearemos la función SendDlgItemMessage.

De nuevo ilustraremos el ejemplo usando los dos métodos:

#define DESHABILITADO -1
...
    static DATOS *Datos;
...
        case WM_INITDIALOG:
           Datos = (DATOS*)lParam; 
           // Estado inicial de los checkbox
           CheckDlgButton(hDlg, ID_NORMAL, 
              Datos->Normal ? BST_CHECKED : BST_UNCHECKED);
           CheckDlgButton(hDlg, ID_AUTO, 
              Datos->Auto ? BST_CHECKED : BST_UNCHECKED);
           if(Datos->TriState != DESHABILITADO)
              CheckDlgButton(hDlg, ID_TRISTATE, 
                 Datos->TriState ? BST_CHECKED : BST_UNCHECKED);
           else
              CheckDlgButton(hDlg, ID_TRISTATE, BST_INDETERMINATE);
           if(Datos->AutoTriState != DESHABILITADO)
              CheckDlgButton(hDlg, ID_AUTOTRISTATE, 
                 Datos->AutoTriState ? BST_CHECKED : BST_UNCHECKED);
           else
              CheckDlgButton(hDlg, ID_AUTOTRISTATE, BST_INDETERMINATE);
           CheckDlgButton(hDlg, ID_AUTOPUSH, 
              Datos->AutoPush ? BST_CHECKED : BST_UNCHECKED);
           // Usando mensajes:
           if(Datos->AutoTriPush != DESHABILITADO)
              SendDlgItemMessage(hDlg, ID_AUTOTRIPUSH, BM_SETCHECK, 
                 Datos->AutoTriPush ? (WPARAM)BST_CHECKED : (WPARAM)BST_UNCHECKED, 0);
           else
              SendDlgItemMessage(hDlg, ID_AUTOTRIPUSH, BM_SETCHECK, 
                 (WPARAM)BST_INDETERMINATE, 0);
           SendDlgItemMessage(hDlg, ID_DERECHA, BM_SETCHECK, 
              Datos->Derecha ? (WPARAM)BST_CHECKED : (WPARAM)BST_UNCHECKED, 0);
           SendDlgItemMessage(hDlg, ID_PLANO, BM_SETCHECK, 
              Datos->Plano ? (WPARAM)BST_CHECKED : (WPARAM)BST_UNCHECKED, 0);
           SetFocus(GetDlgItem(hDlg, ID_NORMAL));
           return FALSE;

Con esto, el estado inicial de los CheckBoxes será correcto.

Procesar mensajes de los CheckBox

En ciertos casos, será necesario procesar algunos de los mensajes procedentes de los CheckBoxes.

Concretamente, en el caso de los CheckBox no automáticos, su estado no cambia cuando el usuario actúa sobre ellos, sino que será el programa quien deba actualizar ese estado.

Para actualizar el estado de los CheckBoxes cada vez que el usuario actúe sobre ellos debemos procesar los mensajes WM_COMMAND procedentes de ellos.

Quizás, la primera intención sea modificar las variables almacenadas en la estructura Datos para adaptarlas a los nuevos valores. Pero recuerda que es posible que el usuario pulse el botón de "Cancelar". En ese caso, los valores de la estructura Datos no deberían cambiar.

Tenemos dos opciones. Una es usar variables auxiliares para almacenar el estado actual de los CheckBoxes. Otra es leer el estado de los controles directamente. Este último sistema es más seguro, ya que previene el que las variables auxiliares y los controles tengan valores diferentes.

Para leer el estado de los controles tenemos dos posibilidades, como siempre: usar la función IsDlgButtonChecked o el mensaje BM_GETCHECK.

        case WM_COMMAND:
           switch(LOWORD(wParam)) {
              case ID_NORMAL:
                 if(SendDlgItemMessage(hDlg, ID_NORMAL, BM_GETCHECK, 0, 0) == BST_CHECKED)
                    SendDlgItemMessage(hDlg, ID_NORMAL, BM_SETCHECK, 
                       (WPARAM)BST_UNCHECKED, 0);
                 else
                    SendDlgItemMessage(hDlg, ID_NORMAL, BM_SETCHECK, 
                       (WPARAM)BST_CHECKED, 0);
                 return TRUE;

              case ID_TRISTATE:
                 if(IsDlgButtonChecked(hDlg, ID_TRISTATE) == BST_INDETERMINATE)
                    CheckDlgButton(hDlg, ID_TRISTATE, BST_UNCHECKED);
                 else if(IsDlgButtonChecked(hDlg, ID_TRISTATE) == BST_CHECKED) 
                    CheckDlgButton(hDlg, ID_TRISTATE, BST_INDETERMINATE);
                 else
                    CheckDlgButton(hDlg, ID_TRISTATE, BST_CHECKED);
                 return TRUE;
            }

En este ejemplo intentamos simular el comportamiento de los CheckBoxes automáticos, pero lo normal es que para eso se usen CheckBoxes automáticos. Los no automáticos pueden tener el comportamiento que nosotros prefiramos, en eso consiste su utilidad.

Devolver valores a la aplicación

Por supuesto, lo normal también será que queramos retornar los valores actuales seleccionados en cada CheckBox. A veces también necesitaremos leer el estado de algún control CheckBox durante la ejecución, por ejemplo, cuando eso influye en el estado de otros controles.

Cuando sólo nos interese devolver valores antes de cerrar el diálogo, leeremos esos valores al procesar el mensaje WM_COMMAND para el botón de "Aceptar".

Ya sabemos los dos modos de obtener el estado de los controles CheckBox, la función IsDlgButtonChecked y el mensaje BM_GETCHECK. Ahora sólo tenemos que añadir la lectura de ese estado al procesamiento del mensaje WM_COMMAND del botón "Aceptar".

              case IDOK:
                 Datos->Normal = (IsDlgButtonChecked(hDlg, ID_NORMAL) == BST_CHECKED);
                 Datos->Auto = (IsDlgButtonChecked(hDlg, ID_AUTO) == BST_CHECKED);
                 if(IsDlgButtonChecked(hDlg, ID_TRISTATE) == BST_INDETERMINATE)
                    Datos->TriState = DESHABILITADO;
                 else
                    Datos->TriState = (IsDlgButtonChecked(hDlg, ID_TRISTATE) == BST_CHECKED);
                 if(IsDlgButtonChecked(hDlg, ID_AUTOTRISTATE) == BST_INDETERMINATE)
                    Datos->AutoTriState = DESHABILITADO;
                 else
                    Datos->AutoTriState = (IsDlgButtonChecked(hDlg, ID_AUTOTRISTATE) == BST_CHECKED);
                 Datos->AutoPush = (IsDlgButtonChecked(hDlg, ID_AUTOPUSH) == BST_CHECKED);
                 if(IsDlgButtonChecked(hDlg, ID_AUTOTRIPUSH) == BST_INDETERMINATE)
                    Datos->AutoTriPush = DESHABILITADO;
                 else
                    Datos->AutoTriPush = (IsDlgButtonChecked(hDlg, ID_AUTOTRIPUSH) == BST_CHECKED);
                 Datos->Derecha = (IsDlgButtonChecked(hDlg, ID_DERECHA) == BST_CHECKED);
                 Datos->Plano = (IsDlgButtonChecked(hDlg, ID_PLANO) == BST_CHECKED);
                 EndDialog(hDlg, FALSE);
                 return TRUE;
              case IDCANCEL:
                 EndDialog(hDlg, FALSE);
                 return FALSE;

Y de momento esto es todo lo que diremos sobre CheckBoxes.

Ejemplo 14


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 14 win014.zip 2004-05-17 3741 bytes 432