Capítulo 8 Control básico ListBox

Los controles edit son muy útiles cuando la información a introducir por el usuario es imprevisible o existen muchas opciones. Pero cuando el número de opciones no es muy grande y son todas conocidas, es preferible usar un control ListBox.

Ese es el siguiente control básico que veremos. Un ListBox consiste en una ventana rectangular con una lista de cadenas entre las cuales el usuario puede escoger una o varias.

El usuario puede seleccionar una cadena apuntándola y haciendo clic con el botón del ratón. Cuando una cadena se selecciona, se resalta y se envía un mensaje de notificación a la ventana padre. También se puede usar una barra de scroll con los listbox para desplazar listas muy largas o demasiado anchas para la ventana.

Ficheros de recursos

Empezaremos definiendo el control listbox en el fichero de recursos, y lo añadiremos a nuestro diálogo de prueba:

#include <windows.h>;
#include "win007.h"

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

DialogoPrueba DIALOG 0, 0, 118, 135
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION
CAPTION "Diálogo de prueba"
FONT 8, "Helv"
BEGIN
 CONTROL "Lista:", -1, "static", 
    SS_LEFT | WS_CHILD | WS_VISIBLE, 
    8, 9, 28, 8
 CONTROL "", ID_LISTA, "listbox", 
    LBS_STANDARD | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    9, 19, 104, 99
 CONTROL "Aceptar", IDOK, "BUTTON", 
    BS_DEFPUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    8, 116, 45, 14
 CONTROL "Cancelar", IDCANCEL, "BUTTON", 
    BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
    61, 116, 45, 14
END

Hemos añadido el control listbox a continuación del control static. Para más detalles acerca de los controles listbox ver control listbox.

Ahora veamos cómo hemos definido nuestro control listbox:

CONTROL "", ID_LISTA, "listbox", 
   LBS_STANDARD | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 
   9, 19, 104, 99
  • 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 listbox no tiene ninguna función. Lo dejaremos como cadena vacía.
  • id es el identificador del control. Los controles listbox necesitan un identificador para que la aplicación pueda acceder a ellos. Usaremos un identificador definido en win007.h.
  • class es la clase de control, en nuestro caso "LISTBOX".
  • style es el estilo de control que queremos. En nuestro caso es una combinación de un estilo listbox y varios de ventana:
    • LBS_STANDARD: ordena alfabéticamente las cadenas en el listbox. La ventana padre recibe in mensaje de entrada cada vez que el usuario hacer click o doble click sobre una cadena. El list box tiene bordes en todos sus lados.
    • 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.

Iniciar controles listbox

Para este ejemplo también usaremos variables estáticas en el procedimiento de ventana para almacenar el valor de la cadena del listbox actualmente seleccionada.

// Datos de la aplicación
typedef struct stDatos {
   char Item[80];
} DATOS;

Daremos valores iniciales a las variables, al procesar el mensaje WM_CREATE del procedimiento de ventana:

    static DATOS Datos;
...    
        case WM_CREATE:
           hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
           /* Inicialización de los datos de la aplicación */
           strcpy(Datos.Item, "Cadena nº 3");
           return 0;

Y pasaremos un puntero a la estructura con los datos como parámetro lParam de la función DialogBoxParam.

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

La característica más importante de los listbox es que contienen listas de cadenas. Así que es imprescindible iniciar este tipo de controles, introduciendo las cadenas antes de que se muestre el diálogo. Eso se hace durante el proceso del mensaje WM_INITDIALOG dentro del procedimiento de diálogo. En este mismo mensaje obtenemos el puntero a la estructura de los datos que recibimos en el parámetro lParam.

    static DATOS *Datos;
...
        case WM_INITDIALOG:
           Datos = (DATOS *)lParam; 
           // Añadir cadenas. Mensaje: LB_ADDSTRING
           SendDlgItemMessage}(hDlg, ID_LISTA, LB_ADDSTRING, 0, (LPARAM)"Cadena nº 1");
           SendDlgItemMessage}(hDlg, ID_LISTA, LB_ADDSTRING, 0, (LPARAM)"Cadena nº 4");
           SendDlgItemMessage}(hDlg, ID_LISTA, LB_ADDSTRING, 0, (LPARAM)"Cadena nº 3");
           SendDlgItemMessage}(hDlg, ID_LISTA, LB_ADDSTRING, 0, (LPARAM)"Cadena nº 2");
           SendDlgItemMessage}(hDlg, ID_LISTA, LB_SELECTSTRING, (UINT)-1, (LPARAM)Datos->Item);
           SetFocus}(GetDlgItem(hDlg, ID_LISTA));
           return FALSE;

Para añadir cadenas a un listbox se usa el mensaje LB_ADDSTRING mediante la función SendDlgItemMessage, que envía un mensaje a un control.

También podemos preseleccionar alguna de las cadenas del listbox, aunque esto no es muy frecuente ya que se suele dejar al usuario que seleccione una opción sin sugerirle nada. Para seleccionar una de las cadenas también se usa un mensaje: LB_SELECTSTRING. Usaremos el valor -1 en wParam para indicar que se busque en todo el listbox.

Devolver valores a la aplicación

También queremos que cuando el usuario está satisfecho con los datos que ha introducido, y pulse el botón de aceptar, el dato de nuestra aplicación se actualice con el texto del ítem seleccionado.

De nuevo recurriremos a mensajes para pedirle al listbox el valor de la cadena actualmente seleccionada. En este caso se trata dos mensajes combinados, uno es LB_GETCURSEL, que se usa para averiguar el índice de la cadena actualmente seleccionada. El otro es LB_GETTEXT, que devuelve la cadena del índice que le indiquemos.

Cuando trabajemos con memoria dinámica y con ítems de longitud variable, será interesante saber la longitud de la cadena antes de leerla desde el listbox. Para eso podemos usar el mensaje LB_GETTEXTLEN.

Haremos esa lectura al procesar el comando IDOK, que se genera al pulsar el botón "Aceptar".

    UINT indice;
...
        case WM_COMMAND:
           switch(LOWORD(wParam)) {
              case IDOK: 
                 indice = SendDlgItemMessage(hDlg, ID_LISTA, LB_GETCURSEL, 0, 0);
                 SendDlgItemMessage(hDlg, ID_LISTA, LB_GETTEXT, indice, (LPARAM)Datos->Item);
                 EndDialog(hDlg, FALSE);
                 break;
              case IDCANCEL:
                 EndDialog(hDlg, FALSE);
                 break;
           }
           return TRUE;

Ejemplo 7


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 7 win007.zip 2004-05-17 3121 bytes 1030

Comentarios de los usuarios (4)

Borja
2013-02-11 10:41:29

En los últimos ejemplos estás definiendo una variable global DATOS que está de más, ya que ya defines una variable estática DATOS.

Diego
2013-02-25 02:29:24

Hola, como detecto si no han seleccionado ninguna item de la listbox.

Salvador Pozo
2013-02-25 11:00:02

Hola Diego:

Si te fijas en el mensaje LB_GETCURSEL:

http://winapi.conclase.net/curso/?winmsg=LB_GETCURSEL

Verás que en el caso de que no haya ningún ítem seleccionado, el valor de retorno es LB_ERR. De modo que este mensaje sólo puede devolver el valor del índice del ítem seleccionado o LB_ERR.

Esto es cierto si el ListBox es de selección única, existen ListBox de selección múltiple, pero esos los vemos en el capítulo 40.

Hasta pronto.

Salvador Pozo
2013-02-25 11:10:47

Hola Borja:

No es así. Si te fijas bien, DATOS es un tipo, no una declaración de una variable global.

/* Declaraciones de tipos */
typedef struct stDatos {
   char Item[80];
} DATOS;

Esto nos permite declarar la variable estática Datos dentro del procedimiento de ventana:

    static DATOS Datos;

En C es obligatorio especificar la palabra reservada struct al declarar variables de tipo estructura, a no ser que definamos un nuevo tipo usando typedef, que es lo que hemos hecho en este ejemplo. Esto ayuda a que el código sea más legible, o al menos esa es mi opinión.