Capítulo 15 Control básico RadioButton

De nuevo estamos hablando de un estilo de botón.

Los RadioButtons sólo pueden tomar dos valores, encendido y apagado. Se usan típicamente para leer opciones que sólo tienen un número limitado y pequeño de posibilidades y sólo un valor posible, como por ejemplo: sexo (hombre/mujer), estado civil (soltero/casado/viudo/divorciado), etc.

Es necesario agrupar usando un GroupBox, al menos dos, y con frecuencia tres o más controles de este tipo. No tiene sentido colocar un solo control RadioButton, ya que al menos uno de cada grupo debe estar activo. Tampoco es frecuente agrupar dos, ya que para eso se puede usar un único control CheckBox. Tampoco se agrupan demasiados, ya que ocupan mucho espacio, en esos casos es mejor usar un ComboBox o un ListBox.

El aspecto normal es el de un pequeño círculo con un texto a uno de los lados, normalmente a la derecha. Cuando está activo se muestra el círculo relleno, cuando no lo está, aparece vacío. También es posible mostrar el RadioButton 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 RadioButtons:

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

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

DialogoPrueba DIALOG 0, 0, 179, 89
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "RadioButtons"
FONT 8, "Helv"
BEGIN
 CONTROL "Grupo 1", ID_GRUPO1, "BUTTON", 
    BS_GROUPBOX | WS_CHILD | WS_VISIBLE | WS_GROUP, 
    4, 5, 76, 52
 CONTROL "RadioButton 1", ID_RADIOBUTTON1, "BUTTON", 
    BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    11, 15, 60, 12
 CONTROL "RadioButton 2", ID_RADIOBUTTON2, "BUTTON", 
    BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    11, 28, 60, 12
 CONTROL "RadioButton 3", ID_RADIOBUTTON3, "BUTTON", 
    BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    11, 41, 60, 12
 CONTROL "Grupo 2", ID_GRUPO2, "BUTTON", 
    BS_GROUPBOX | WS_CHILD | WS_VISIBLE | WS_GROUP, 
    89, 5, 76, 52
 CONTROL "RadioButton 4", ID_RADIOBUTTON4, "BUTTON", 
    BS_RADIOBUTTON | BS_PUSHLIKE | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    96, 15, 60, 12
 CONTROL "RadioButton 5", ID_RADIOBUTTON5, "BUTTON", 
    BS_RADIOBUTTON | BS_PUSHLIKE | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    96, 28, 60, 12
 CONTROL "RadioButton 6", ID_RADIOBUTTON6, "BUTTON", 
    BS_RADIOBUTTON | BS_PUSHLIKE | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    96, 41, 60, 12
 CONTROL "Aceptar", IDOK, "BUTTON", 
    BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    8, 69, 50, 14
 CONTROL "Cancelar", IDCANCEL, "BUTTON", 
    BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    68, 69, 50, 14
END

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

Como se puede observar, un RadioButton es un tipo de botón con uno de los siguientes estilos BS_RADIOBUTTON o BS_AUTORADIOBUTTON.

También se puede ver que hemos agrupado los controles RadioButton en dos grupos de tres botones. Cada grupo empieza con un control GroupBox con el estilo WS_GROUP. Más adelante veremos cómo trabajan en conjunto los GroupBoxes y los RadioButtons.

 CONTROL "Normal", ID_RADIOBUTTON1, "BUTTON", BS_RADIOBUTTON | 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 RadioButtons será el texto que aparecerá acompañando al círculo o en el interior del botón y que servirá para identificar el valor a elegir.
  • id es el identificador del control. El identificador será necesario en algunos casos para procesar los comandos procedentes del RadioButton, 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_RADIOBUTTON: Indica que se trata de un RadioButton no automático.
    • BS_AUTORADIOBUTTON: Indica que se trata de un RadioButton automático.
    • BS_PUSHLIKE: Indica que se trata de un RadioButton con la apariencia de un botón corriente.
    • BS_LEFTTEXT: Indica que el texto del RadioButton 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 RadioButton

Los controles RadioButton necesitan ser inicializados. Para este ejemplo también usaremos variables globales para almacenar los valores de las variables que se pueden editar con los RadioButtons. En general se necesita una única variable para cada grupo de RadioButtons.

// Datos de la aplicación
typedef struct stDatos {
   int Grupo1;
   int Grupo2;
} DATOS;

Crearemos una estructura de datos estática en nuestro procedimiento de ventana, y le daremos valores iniciales al procesar el mensaje WM_CREATE:

    static DATOS Datos;
...
        case WM_CREATE:
           hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
           /* Inicialización */
           Datos.Grupo1 = 1;
           Datos.Grupo2 = 2;
           return 0;

Finalmente, usaremos la función DialogBoxParam para crear el diálogo, y pasaremos en el parámetro lParam un puntero a la 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 CheckRadioButton o el mensaje BM_SETCHECK, en este último caso, emplearemos la función SendDlgItemMessage. Usar el mensaje implica enviar un mensaje al menos a dos controles RadioButton del grupo, el que se activa y el que se desactiva.

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

    static DATOS *Datos;
...
        case WM_INITDIALOG:
           Datos = (DATOS*)lParam; 
           // Estado inicial de los radiobuttons
           CheckRadioButton(hDlg, ID_RADIOBUTTON1, ID_RADIOBUTTON3, 
              ID_RADIOBUTTON1+Datos->Grupo1-1);
           // Usando mensajes:
           SendDlgItemMessage(hDlg, ID_RADIOBUTTON4, 
              BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);
           SendDlgItemMessage(hDlg, ID_RADIOBUTTON5, 
              BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);
           SendDlgItemMessage(hDlg, ID_RADIOBUTTON6, 
              BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);
           SendDlgItemMessage(hDlg, ID_RADIOBUTTON4+Datos->Grupo2-1, 
              BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
           SetFocus(GetDlgItem(hDlg, ID_RADIOBUTTON1));
           return FALSE;

Es muy importante asignar identificadores correlativos a los controles de cada grupo. Esto nos permite por una parte usar la función CheckRadioButton, tanto como expresiones como ID_RADIOBUTTON1+Datos>Grupo1-1. (Si hubierámos empezado por cero para el primer control del grupo, no sería necesario restar uno).

Usando los mensajes nos vemos obligados a quitar la marca a todos los controles del grupo 2. Esto es porque desconocemos el estado inicial de los controles. En este caso es mucho mejor usar la función que el mensaje.

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

Procesar mensajes de los RadioButtons

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

Concretamente, en el caso de los RadioButtons 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 RadioButtons cada vez que el usuario actúe sobre ellos debemos procesar los mensajes WM_COMMAND procedentes de ellos.

Como sucedía con los CheckBoxes, la primera intención puede que 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.

De nuevo tenemos dos opciones. Una es usar variables auxiliares para almacenar el estado actual de los RadioButtons. 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. Veremos las dos opciones.

Con funciones:

           switch(LOWORD(wParam)) {
              case ID_RADIOBUTTON4:
              case ID_RADIOBUTTON5:
              case ID_RADIOBUTTON6:
                 CheckRadioButton(hDlg, ID_RADIOBUTTON4, ID_RADIOBUTTON6, 
                    LOWORD(wParam));
                 return TRUE;

Con mensajes:

           switch(LOWORD(wParam)) {
              case ID_RADIOBUTTON4:
              case ID_RADIOBUTTON5:
              case ID_RADIOBUTTON6:
                 SendDlgItemMessage(hDlg, ID_RADIOBUTTON4, BM_SETCHECK, 
                    (WPARAM)BST_UNCHECKED, 0);
                 SendDlgItemMessage(hDlg, ID_RADIOBUTTON5, BM_SETCHECK, 
                    (WPARAM)BST_UNCHECKED, 0);
                 SendDlgItemMessage(hDlg, ID_RADIOBUTTON6, BM_SETCHECK, 
                    (WPARAM)BST_UNCHECKED, 0);
                 SendDlgItemMessage(hDlg, LOWORD(wParam), BM_SETCHECK, 
                    (WPARAM)BST_CHECKED, 0);
                 return TRUE;

En este ejemplo intentamos simular el comportamiento de los RadioButtons automáticos, pero lo normal es que para eso se usen RadioButtons automáticos. Los no automáticos pueden tener el comportamiento que nosotros prefiramos, y en eso consiste su utilidad. Los automáticos no requieren ninguna atención de nuestro programa.

Devolver valores a la aplicación

Por supuesto, lo normal también será que queramos retornar los valores actuales seleccionados en cada Grupo de RadioButtons.

Cuando 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 RadioButtons, 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:
                 if(IsDlgButtonChecked(hDlg, ID_RADIOBUTTON1) == BST_CHECKED) 
                    Datos->Grupo1 = 1;
                 else 
                 if(IsDlgButtonChecked(hDlg, ID_RADIOBUTTON2) == BST_CHECKED) 
                    Datos->Grupo1 = 2;
                 else 
                    Datos->Grupo1 = 3;
                 if(SendDlgItemMessage(hDlg, ID_RADIOBUTTON4, 
                    BM_GETCHECK, 0, 0) == BST_CHECKED) Datos->Grupo2 = 1;
                 else 
                 if(SendDlgItemMessage(hDlg, ID_RADIOBUTTON5, 
                    BM_GETCHECK, 0, 0) == BST_CHECKED) Datos->Grupo2 = 2;
                 else                                  Datos->Grupo2 = 3;
                 EndDialog(hDlg, FALSE);
                 return TRUE;
              case IDCANCEL:
                 EndDialog(hDlg, FALSE);
                 return FALSE;

Con esto queda cerrado de momento el tema de los RadioButtons.

Ejemplo 15


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 15 win015.zip 2004-05-17 3373 bytes 471