El dato del ítem

En todos los ejemplos que hemos visto siempre hemos recuperado cadenas de un list box, pero también podemos trabajar con índices. Por ejemplo, crearemos un programa que nos muestre las capitales y superficies de varios países, que podremos seleccionar de un list box. Para ello almacenaremos esos datos en un array:

struct Pais {
   char *Nombre;
   char *Capital;
   int Superficie;
} paises[22] =
   {
      "Argentina", "Buenos Aires", 2766890,
      "Mexico", "Mexico DC", 1972550,
      "Brasil", "Brasilia", 8514876,
      "Peru", "Lima", 1285220,
      "Colombia", "Bogotá", 1138910,
      "Bolivia", "La Paz", 1098580,
      "Venezuela", "Caracas", 912050,
      "Chile", "Santiago", 756096,
      "España", "Madrid", 504782,
      "Paraguay", "Asunción", 406750,
      "Ecuador", "Quito", 283560,
      "Uruguay", "Montevideo", 176220,
      "Nicaragua", "Managua", 129494,
      "Honduras", "Tegucigalpa", 112090,
      "Cuba", "La Habana", 110860,
      "Guatemala", "Guatemala", 108890,
      "Portugal", "Lisboa", 92391,
      "Panamá", "Panamá", 78200,
      "Costa Rica", "San José", 51100,
      "República Dominicana", "Santo Domingo", 48730,
      "El Salvador", "San Salvador", 21040,
      "Puerto Rico", "San Juan", 9104
   };

Generalmente trabajaremos con list boxes definidos con el estilo LBS_STANDARD, lo cual implica, entre otras cosas, que las cadenas se muestran por orden alfabético.

En nuestro ejemplo esto plantea un problema. El array está ordenado por superficies, no alfabéticamente, por lo tanto, una vez insertadas las cadenas, los índices de los ítems en el list box no coincidirán con los índices en el array, lo cual sería muy útil, ya que podríamos recuperar el índice del ítem activo y mostrar la información correspondiente a ese índice en el array.

Para evitar esto, por supuesto, podemos eliminar el estilo LBS_SORT, con lo que el orden del list box coincidiría con el del array. Pero esto no nos interesa, ya que complica la tarea del usuario, que debe buscar en un list box aparentemente desordenado.

Otra forma de evitarlo es leer la cadena seleccionada y buscarla en el array de forma secuencial. Sin embargo, esta solución no es muy elegante, y sería francamente mala si la lista contiene muchos elementos.

También podemos ordenar el array alfabéticamente, pero esta solución tampoco es satisfactoria, ya que podría haber errores de orden, o sencillamente, podríamos usar el mismo array para crear un list box de capitales.

El API nos permite usar otra solución. Ya sabemos que cada ítem tiene asociado un índice y una cadena. Pero también tiene asociado un dato entero de 32 bits: el ítem data, o dato de ítem.

A cada ítem le podemos asignar un valor entero mediante el mensaje LB_SETITEMDATA, y recuperarlo mediante LB_GETITEMDATA.

Podemos aprovechar que el valor de retorno del mensaje LB_ADDSTRING es el índice del ítem insertado, y usar ese valor en el mensaje LB_SETITEMDATA, para asignar el valor del índice en el array:

void IniciarLista(HWND hctrl)
{
   int i;
   int actual;
   
   for(i = 0; i < 22; i++) {
      actual = SendMessage(hctrl, LB_ADDSTRING, 0, (LPARAM)paises[i].Nombre);
      SendMessage(hctrl, LB_SETITEMDATA, (WPARAM)actual, i);
   }
}

De este modo, podremos recuperar los datos del array correspondientes a un ítem:

        case WM_PAINT:
           i = SendMessage(hctrl, LB_GETCURSEL, 0, 0);
           i = SendMessage(hctrl, LB_GETITEMDATA, (WPARAM)i, 0);
           hdc = BeginPaint(hwnd, &ps);
           SetBkMode(hdc, TRANSPARENT);
           sprintf(cad, "País: %s", paises[i].Nombre);
           TextOut(hdc, 300, 20, cad, strlen(cad));
           sprintf(cad, "Capital: %s", paises[i].Capital);
           TextOut(hdc, 300, 40, cad, strlen(cad));
           sprintf(cad, "Superficie: %d Km²", paises[i].Superficie);
           TextOut(hdc, 300, 60, cad, strlen(cad));
           EndPaint(hwnd, &ps);
           break;

Ya veremos que el dato de ítem tiene otras utilidades, pero en muchos casos nos proporciona una forma útil de almacenar un dato relativo a un ítem. Al tratarse de un entero de 32 bits también puede contener punteros.

Ejemplo 58


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 58 win058.zip 2007-03-15 3635 bytes 143

Funciones para ficheros y directorios

Una de las aplicaciones más frecuentes de los list box es la elección de ficheros. Por ese motivo, el API proporciona algunas funciones para iniciar y seleccionar ítems en un list box a partir de los datos de directorios.

La función DlgDirList nos permite iniciar el contenido de un list box a partir de los ficheros, carpetas, unidades de disco, etc.

Esta función necesita cinco parámetros. El primero es un manipulador de la ventana o diálogo que contiene el list box que vamos a inicializar. El segundo es un puntero a una cadena con el camino del directorio a mostrar. Esta cadena tiene que tener espacio suficiente, ya que la función puede modificar su contenido. El tercer parámetro es el identificador del list box. El cuarto el identificador de un control estático, que se usa para mostrar el camino actualmente mostrado en el list box. El último parámetro nos permite seleccionar el tipo de entradas que se mostrarán en el list box.

Mediante este último parámetro podemos restringir el tipo de entradas, impidiendo o permitiendo que se muestren directorios o unidades de almacenamiento, o limitando los atributos de los ficheros y directorios a mostrar.

Ya hemos dicho que se necesita un control estático. Como aún no hemos visto el modo de insertar estos controles directamente en la ventana, para este ejemplo lo haremos sin más explicaciones, aunque como se puede ver, no tiene nada de raro:

   HWND hestatico;
...
           hestatico = CreateWindowEx(
              0,
              "STATIC",        /* Nombre de la clase */
              "",              /* Texto del título, no tiene */
              WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, /* Estilo */ 
              9, 4,            /* Posición */
              344, 18,         /* Tamaño */
              hwnd,            /* Ventana padre */
              (HMENU)ID_TITULO,/* Identificador del control */
              hInstance,       /* Instancia */
              NULL);           /* Sin datos de creación de ventana */ 
           SendMessage(hestatico, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));

Por supuesto, podemos usar los comodines '*' y '?' para los nombres de fichero.

Veamos un ejemplo para uniciar un list box a partir del directorio actual, mostrando los discos y directorios, y limitando los ficheros a los que se ajusten a *.c:

...
   IniciarLista(hwnd, "*.c");
...

void IniciarLista(HWND hwnd, char* p)
{
   char path[512];
   
   strcpy(path, p);
   
   DlgDirList(
    hwnd,           /* manipulador de cuadro de diálogo con list box  */
    path,           /* puntero a cadena de camino o nombre de fichero */
    ID_LISTA,       /* identificador de list box                      */
    ID_TITULO,      /* identificador de control estático              */
    DDL_DIRECTORY | DDL_DRIVES /* atributos de ficheros a mostrar     */
   );   
}

La función DlgDirSelectEx nos permite leer la selección actual de una lista inicializada mediante la función DlgDirList. Si el valor de retorno de esta función es distinto de cero, la selección actual es un directorio o unidad de almacenamiento, por lo que será posible hacer un cambio de directorio. Si el valor de retorno es cero, se trata de un fichero.

Aprovecharemos esto para navegar a lo largo de los discos de nuestro ordenador, para lo que responderemos al mensaje de notificación LBN_DBLCLK, cambiando a la nueva ubicación o mostrando el nombre del fichero seleccionado:

        case WM_COMMAND:
           switch(LOWORD(wParam)) {
              case ID_LISTA:
                 switch(HIWORD(wParam)) {
                   case LBN_DBLCLK:
                       if(DlgDirSelectEx(hwnd, cad, 512, ID_LISTA)) {
                         strcat(cad, "*.c");
                         IniciarLista(hwnd, cad);
                      } else
                      else
                         MessageBox(hwnd, cad, "Fichero seleccionado", MB_OK);
                      break;
...

También existen dos mensajes relacionados con este tema.

El mensaje LB_DIR tiene un uso equivalente a la función DlgDirList. En el parámetro wParam se indican los atributos de los ficheros a mostrar, así como si se deben mostrar directorios y unidades de almacenamiento. En el parámetro lParam se suministra el nombre de fichero, (que puede tener comodines) o el camino de los ficheros a insertar.

Por ejemplo, podemos añadir los ficheros de cabecera al contenido del list box, de modo que se muestren los ficheros fuente en c y los de cabecera:

void IniciarLista(HWND hwnd, char* p)
{
   char path[512];
   
   strcpy(path, p);
   
   DlgDirList(hwnd, path, ID_LISTA, ID_TITULO, 
      DDL_DIRECTORY | DDL_DRIVES);

   strcpy(path, "*.h");
   SendMessage(GetDlgItem(hwnd, ID_LISTA), LB_DIR, 
      (WPARAM)0, (LPARAM)path);

Pero este mensaje no está previsto para usarse junto a la función DlgDirList, ya que tienen objetivos parecidos. En su lugar podemos usar otro mensaje, que nos permite añadir ficheros a una lista previamente creada.

Este otro mensaje, LB_ADDFILE, nos permite añadir ficheros sueltos al contenido del list box. Los ficheros sólo se añaden si existen, y también pueden usar comodines. Este ejemplo añadiría los ficheros ejecutables que existan en el directorio actualmente mostrado:

...
   SendMessage(GetDlgItem(hwnd, ID_LISTA), LB_ADDFILE,
      0, (LPARAM)"*.exe");

Ejemplo 59


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 59 win059.zip 2007-03-15 3117 bytes 132