List box sin selección

Además de los list box de selección sencilla y de selección múltiple, también es posible crear list boxes sin selección. Para ello bastará con crear el list box con el estilo LBS_NOSEL.

Podemos usar estos list boxes para mostrar listas de valores para una consulta por parte del usuario, pero que no precisen una selección.

List box multicolumna

Los ítems en un list box no tienen por qué mostrarse en una única columna, como hemos hecho en los ejemplos anteriores. Si se especifica el estilo LBS_MULTICOLUMN se aprovechará toda la anchura del list box para mostrar varias columnas de ítems.

En los list boxes de varias columnas se aprovecha toda la altura del control para mostrar tantos ítems como sea posible, y el resto se muestran en otras columnas. Se añadirán tantas columnas como sea necesario, y será posible desplazarse horizontalmente, aunque para poder usar la barra de desplazamiento horizontal habrá que especificar el estilo WS_HSCROLL al crear el control. Las columnas que no quepan en el área del list box no serán visibles pero al desplazarnos lateralmente se mostrán nuevas columnas y se ocultarán por el lado contrario.

Disponemos de un mensaje para establecer la anchura de las columnas, se trata de LB_SETCOLUMNWIDTH. En el parámetro wParam indicaremos la anchura de las columnas, en pixels:

SendMessage(hctrl, LB_SETCOLUMNWIDTH, (WPARAM)40, 0);

Ejemplo 61


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 61 win061.zip 2007-03-15 3039 bytes 146

Paradas de tabulación

En principio no es posible crear list boxes en los que a cada fila corresponda un ítem, y que para cada ítem se creen varias columnas con informaciones diferentes. (Esto es algo que se hace con otro tipo de control que veremos más adelante: los list view).

Sin embargo, existe una forma limitada de hacer algo parecido. Consiste en usar el estilo LBS_USETABSTOPS, y en separar las columnas dentro de cada ítem con caracteres de tabulación. Si no se especifica este estilo, los caracteres de tabulación no se expanden en espacios, y el list box no tendrá el aspecto de una tabla.

Podemos definir la anchura de cada columna mediante el mensaje LB_SETTABSTOPS, indicando en el parámetro wParam el número de paradas de tabulación y en el parámetro lParam un array con las separaciones de cada parada en pixels:

   int tab[4] = {50,80,130,160};
   int i;
   char cad[128];
...
   for(i = 0; i < 40; i++) {
      sprintf(cad, "Ítem %03d\tcol 2\tcolumna 3\tinfo x\t%d", i, i*213);
      SendMessage(hlista, LB_ADDSTRING, 0, (LPARAM)cad);
   }
...
   SendMessage(hctrl, LB_SETTABSTOPS, (WPARAM)4, (LPARAM)tab);

Ejemplo 62


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 62 win062.zip 2007-03-15 2974 bytes 116

Actualizaciones de gran número de ítems

Hay dos posibles situaciones de potencialmente peligrosas en las las actualizaciones que afecten a muchos ítems en un list box.

Por una parte, el proceso puede requerir una cantidad importante de memoria, cuando se añaden muchos ítems.

Por otra parte, el proceso puede requerir mucho tiempo, ya sea porque se deben añadir muchos ítems o porque se deben hacer muchas modificaciones que impliquen el borrado e inserción de ítems.

Optimizar la memoria

En versiones de Windows anteriores al uso de la memoria virtual, era necesario tener en cuenta la memoria disponible antes de insertar un gran número de ítems en un list box. Para eso se usaba el mensaje LB_INITSTORAGE, en el que indicamos en el parámetro wParam el número de ítems a añadir, y en el parámetro lParam la candidad de memoria estimada necesaria para acomodar esos ítems.

   /* Prepararse para insertar 10000 ítems de
   32 bytes por ítem, aproximadamente */
   SendMessage(hctrl, LB_INITSTORAGE, 10000, 320000);
   IniciarLista(hctrl);

No es necesario ser demasiado preciso con la canditad de memoria requerida, se trata sólo de una estimación, si nos quedamos cortos, los ítems que no quepan se insertarán del modo normal. Si nos quedamos largos, la memoria sobrante se podrá aprovechar en nuevas inserciones.

Este mensaje sólo es necesario en Windows 95, en NT no nos preocupa la memoria necesaria para almacenar los ítems, ya que el modelo de memoria virtual dispone de una cantidad prácticamente ilimitada.

Optimizar el tiempo

El problema del tiempo sí es importante. Cada vez que se añade o elimina un ítem, el list box intenta actualizar la pantalla para reflejar los cambios. Esto, cuando los cambios son muy numerosos, hará que aparentemente la aplicación no responda, y que el tiempo invertido en las actualizaciones sea mayor del necesario.

Para evitar esto podemos hacer uso del estilo LBS_NOREDRAW. Si este estilo está activo no se actualizará la ventana del list box aunque se produzcan cambios. Este estilo se puede activar y desactivar mendiante un mensaje WM_SETREDRAW, indicando en el parámetro wParam el valor TRUE para desactivarlo (activar el redibujado) o FALSE para activarlo (desactivar el redibujado).

   SendMessage(hctrl, WM_SETREDRAW, FALSE, 0);
   IniciarLista(hctrl);
   SendMessage(hctrl, WM_SETREDRAW, TRUE, 0);

Ejemplo 63


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 63 win063.zip 2007-03-15 3057 bytes 97

Responder al teclado

Podemos hacer que nuestro control list box responda a determinadas pulsaciones del teclado, cuando tenga el foco, usando el estilo LBS_WANTKEYBOARDINPUT.

Mediante este estilo, la ventana padre del list box recibirá un mensaje WM_VKEYTOITEM cada vez que el usuario pulse una tecla.

Nuestro procedimiento de ventana o diálogo podrá procesar este mensaje y actuar en consecuencia. En el parámetro wParam recibiremos dos valores, en la palabra de menor peso el código de tecla virtual de la tecla pulsada, y en la palabra de mayor peso, la posición del caret. En el parámetro lParam recibiremos el manipulador del control list box.

En el siguiente ejemplo podemos hacer que nuestro list box responda a la tecla 'B' borrando el ítem actualmente seleccionado, y a la tecla 'I' insertando de nuevo los valores iniciales:

        case WM_VKEYTOITEM:
          if((HWND)lParam == hctrl) { /* Asegurarse de que el mensaje proviene de nuestra lista */
             switch(LOWORD(wParam)) {
               case 'B': /* Borrar actual */
                 i = SendMessage(hctrl, LB_GETCURSEL, 0, 0);
                 SendMessage(hctrl, LB_DELETESTRING, (WPARAM)i, 0);
                 SetFocus(hctrl);
                 return -2; /* Tratamiento de tecla terminado */
               case 'I': /* Leer valores iniciales */
                 IniciarLista(hctrl);
                 SetFocus(hctrl);
                 return -2; /* Tratamiento de tecla terminado */
             }
          }
          return -1; /* Acción por defecto */

El valor de retorno es importante. Un valor de -1 indica que el list box debe realizar la acción por defecto definida para la tecla. Esto permite, en nuestro ejemplo, que las teclas del cursor (y en general, todas menos la 'B' y la 'I'), sigan realizando sus funciones normales. Un valor de -2 indica que el list box no tiene que realizar ninguna acción para esta pulsación de tecla. Un valor igual o mayor que 0 se refiere a un índice de un ítem, e indica que el list box debe realizar la acción por defecto con ese índice.

Ejemplo 64


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 64 win064.zip 2007-03-15 3034 bytes 95