Aspectos gráficos del list box

En cuanto al aspecto gráfico del list box tenemos otras opciones que podemos controlar.

Ajustar la anchura de un list box

Por una parte, ya vimos que podemos añadir una barra de desplazamiento horizontal creando nuestro list box con el estilo WS_HSCROLL. Esto lo podemos hacer aunque no se trate de un list box de columnas múltiples. Puede ser útil si la anchura de los ítems sobrepasa la del list box.

Sin embargo, usar este estilo no asegura que la barra de desplazamiento sea mostrada. Para que la barra aparezca hay que ajustar la extensión horizontal del list box mediante un mensaje LB_SETHORIZONTALEXTENT, indicando en el parámetro wParam la nueva extensión horizontal, en pixels.

Si la extensión horizontal es mayor que la anchura del list box, se mostrará la barra de desplazamiento, en caso contrario la barra no aparecerá.

Esto nos plantea una duda, ¿cómo calcular la extensión necesaria según las longitudes de las cadenas contenidas en el list box?

Bueno, podríamos hacerlo a ojo, pero esta técnica es arriesgada, ya que si nos quedamos cortos no será posible visualizar por completo algunos ítems.

Lo mejor es calcular la longitud de cada cadena al insertarla, y si es mayor que la extensión actual, actualizar el valor de la extensión. Para obtener el valor de la extensión actual se usa el mensaje LB_GETHORIZONTALEXTENT.

Claro que esto plantea un problema si se eliminan ítems, ya que nos obligaría a calcular las longitudes de todas las cadenas que quedan en el list box. Sin embargo, podemos ignorar estos casos, y mantener la extensión, ya que la visibilidad de todos los ítems está asegurada.

Para calcular la longitud de una cadena en pixes, vimos en el capítulo 24, que podemos usar la función GetTextExtentPoint32, por ejemplo, en la siguiente función:

int CalculaLongitud(HWND hwnd, char *cad)
{
   HDC hdc;
   SIZE tam;
   HFONT hfont;
   
   hfont = (HFONT)GetStockObject( DEFAULT_GUI_FONT );
   hdc = GetDC(hwnd);
   SelectObject(hdc, hfont);
   GetTextExtentPoint32(hdc, cad, strlen(cad), &tam); 
   /*LPtoDP(hdc, (POINT *)&tam, 1);*/
   ReleaseDC(hwnd, hdc);
   
   return tam.cx;
}

Para que el cálculo sea correcto debemos seleccionar en el DC la misma fuente que usamos en el list box. Además, habría que tener en cuenta que la función GetTextExtentPoint32 devuelve el tamaño de la cadena en unidades lógicas, y en rigor habría que convertir esos valores a unidades de dispositivo. Esto es innecesario, ya que en un control no se realiza ninguna proyección.

Así, cada vez que insertemos un ítem en el list box, deberemos comprobar si resulta ser el más largo:

   char item[300];
   int x;
   int eActual;
   
   eActual = SendMessage(hlista, LB_GETHORIZONTALEXTENT, 0, 0);
   
   strcpy(item, "Ítem de una anchura tal que no cabe en "
        "el list box que hemos definido, o al menos no debería caber, "
        "si las cosas salen tal y como las hemos calculado, claro.");

   x = CalculaLongitud(hlista, cad);
   if(x > eActual) eActual = x;
   SendMessage(hlista, LB_ADDSTRING, 0, (LPARAM)cad);
   SendMessage(hlista, LB_SETHORIZONTALEXTENT, eActual, 0);

Ajustar la altura de los ítems

Por defecto, la altura de los ítems se calcula en función de la fuente asignada al list box. Podemos obtener el valor de la altura del ítem mediante el mensaje LB_GETITEMHEIGHT. Si se trata de un list box con un estilo owner-draw cada ítem puede tener una altura diferente, y se puede especificar el índice del ítem en el parámetro wParam. En los list box normales, el valor de wParam debe ser cero.

   h = SendMessage(hctrl, LB_GETITEMHEIGHT, 0, 0);

Para modificar la altura de un ítem se usa el mensaje LB_SETITEMHEIGHT, en el caso de list boxes con un estilo owner-draw se puede asignar una altura diferente a cada ítem. En ese caso, especificaremos el índice del ítem en el parámetro wParam, y la altura deseada en la palabra de menor peso del parámetro lParam, usando la macro MAKELPARAM. Veremos esto con más detalle al estudiar los estilos owner-draw.

   SendMessage(hctrl, LB_SETITEMHEIGHT, 0, MAKELPARAM(30,0));
   InvalidateRect(hctrl, NULL, TRUE);

Items y coordenadas

Podemos obtener las coordenadas del rectángulo que contiene a un ítem determinado mediante el mensaje LB_GETITEMRECT, indicando en el parámetro wParam el índice del ítem y en el parámetro lParam un puntero a una estructura RECT que recibirá las coordenadas del rectángulo:

   int i;
   RECT re;
...
   i = SendMessage(hctrl, LB_GETCURSEL, 0, 0);
   SendMessage(hctrl, LB_GETITEMRECT, i, (LPARAM)&re);

También podemos obtener el índice del ítem correspondiente a las coordenadas de un punto dentro del list box. Para ello usaremos el mensaje LB_ITEMFROMPOINT, indicando en el parámetro lParam las coordenadas del punto, en la palabra de menor peso la coordenada x y en la de mayor peso, la coordenada y. Usaremos la macro MAKELPARAM para crear el valor del parámetro a partir de las coordenadas:

   int i;
...
   i = SendMessage(hctrl, LB_ITEMFROMPOINT, 0, MAKELPARAM(40, 123));

Ejemplo 65


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 65 win065.zip 2007-03-15 3704 bytes 101

Localizaciones

Ya hemos visto que en los controles list box los ítems se muestran por orden alfabético, al menos en los que hemos usado hasta ahora. Pero el orden alfabético no es algo universal, y puede cambiar dependiendo del idioma.

Generalmente esto no nos preocupará, ya que el idioma usado para elegir el orden se toma del propio sistema. Sin embargo, puede haber casos en que nos interese modificar o conocer el idioma usado en un list box.

Para obtener el valor de la localización actual se usa el mensaje LB_GETLOCALE. El valor de retorno es un entero de 32 bits, en el que la palabra de menor peso contiene el código de país, y el de mayor peso el del lenguaje, este último a su vez, se compone de un identificador de lenguaje primario y un identificador de sublenguaje.

Se pueden usar las macros PRIMARYLANGID y SUBLANGID para obtener el identificador de lenguaje primario y el de sublenguaje, respectivamente.

   int i;
   char cad[120];
...
   i = SendMessage(hctrl, LB_GETLOCALE, 0, 0);
   sprintf(cad, "País %d, id lenguaje primario %d, "
      "id de sublenguaje %d", 
      HIWORD(i), PRIMARYLANGID(LOWORD(i)), SUBLANGID(LOWORD(i)));
   MessageBox(hwnd, cad, "Localización", MB_OK);

También podemos modificar la localización actual mediante un mensaje LB_SETLOCALE, indicando en el parámetro wParam el nuevo valor de localización. Podemos crear uno de estos valores mediante las macros MAKELCID y MAKELANGID:

   SendMessage(hctrl, LB_SETLOCALE, 
                   MAKELCID(MAKELANGID(LANG_SPANISH, SUBLANG_SPANISH),
                            SSORT_DEFAULT), 0);

La macro MAKELCID crea un identificador de localización a partir de un identificador de lenguaje y una constante que debe ser SSORT_DEFAULT.

La macro MAKELANGID crea un identificador de lenguaje a partir de un identificador de lenguaje primario y de un identificador de sublenguaje.

Ejemplo 66


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 66 win066.zip 2007-03-15 3193 bytes 95

Otros estilos

Nos quedan algunas cosas que comentar sobre los estilos de los controles list box.

Generalmente, hasta ahora, hemos basado nuestros list boxes en el estilo LBS_STANDARD. En realidad, este estilo se define como la combinación de dos estilos: LBS_SORT y LBS_NOTIFY.

El estilo LBS_SORT indica que los ítems en el list box deben mostrarse ordenados alfabéticamente. El estilo LBS_NOTIFY indica que se debe enviar un mensaje de notificación a la ventana padre del control cada vez que el usuario haga clic o boble clic sobre un ítem.

Si no se especifica el estilo LBS_SORT, los ítems se muestran en el mismo orden en que se añaden, (excepto aquellos insertados mediante el mensaje LB_INSERTSTRING).

Si no se especifica el estilo LBS_NOTIFY, la ventana padre no recibirá los mensajes de notificación: LBN_DBLCLK, LBN_SELCHANGE y LBN_SELCANCEL.

Además, aunque no se especifique, todos los controles list box que no tengan un estilo owner-draw, se definen con el estilo LBS_HASSTRINGS por defecto. Este estilo indica que los ítems consisten en cadenas de texto, el sistema se encarga de mantener la memoria para almacenar estas cadenas.

También podemos usar el estilo LBS_DISABLENOSCROLL, de modo que la barra de desplazamiento vertical se muestre siempre, aunque todos los ítems puedan ser visualizados. Este estilo no se puede aplicar a los controles con el estilo LBS_MULTICOLUMN.

Por último, hay que comentar algo sobre las dimensiones verticales de los controles list box. Cuando se crea uno de estos controles, la altura del control no se respeta de forma exacta, sino que se reduce lo necesario para que el list box contenga un número exacto de ítems. Así, si por ejemplo, indicamos una altura de 184, y cada ítem ocupa 18, la altura usada para crear el control será de 180, ya que en la distancia especificada caben 18 ítems, pero no 19. De este modo no se mostrará una parte de un ítem, y el control se dibujará más rápidamente.

Este comportamiento se puede evitar mediante el uso del estilo LBS_NOINTEGRALHEIGHT. Los list boxes con este estilo se crearán con la altura exacta indicada al definirlos.