Capítulo 48 Listas de imágenes

Haremos un interludio en este capítulo. Las listas de imágenes no son extrictamente controles, al menos según mi opinión, aunque la documentación de Microsoft los considera como tales. Pero sí los usaremos en otros controles que veremos en próximos capítulos, como en las barras de herramientas, los TreeViews o los ListViews, por lo que será bueno conocerlos antes.

Las listas de imágenes se usan también para operaciones de arrastrar y soltar.

Para poder usar las listas de imágenes en nuestros programas es necesario incluir el fichero de cabecera "commctrl.h", enlazar con la librería "comctl32", y asecurarse de que la dll ha sido cargada invocando a la función InitCommonControls.

Una lista de imágenes es una colección de imágenes del mismo tamaño, a las que podemos acceder mediante un índice. Todas las imágenes se almacenan en un único mapa de bits, una a continuación de otra. Existe la opción de añadir un segundo mapa de bits monocromo con máscaras, de modo que las imágenes se puedan mostrar con una zona transparente, igual que los iconos.

El API suministra funciones y macros para crear y destruir listas de imágenes, añadir o eliminar imágenes de una lista, mostrarlas en pantalla, extraer iconos, etc.

Crear una lista de imágenes

Consideraremos las listas de imágenes como un recurso. Y como tal, las crearemos cuando las necesitemos y las destruiremos cuando ya no sean necesarias. Generalmente usaremos los mensajes de creación de ventanas o diálogos para crear las listas de imágenes, y los mensajes de destrucción para destruirlas.

Crear una lista de imágenes requiere varios pasos. El primero es crear la pripia lista, usando la función ImageList_Create. Esta función necesita varios parámetros:

  • La anchura y altura de las imágenes que contiene.
  • Unas banderas que indican la codificación de color y si se trata de una lista de imágenes con o sin máscara.
  • El número de imágenes iniciales de la lista.
  • Y el número de imágenes en el que la lista crecerá si es necesario añadir imágenes a ella.
    static HIMAGELIST hIml;
    static HIMAGELIST hIml2;
...
        hIml = ImageList_Create(32, 32, ILC_COLOR24, 10, 4);
        hIml2 = ImageList_Create(24, 24, ILC_COLOR8 | ILC_MASK, 15, 2);
...

Este ejemplo crea dos listas de imágenes. La primera con 10 imágenes de 32x32 pixels y 24 bits de profundidad de color. Si fuera necesario añadir imágenes, la lista crecerá en grupos de cuatro imágenes. La segunda con 15 imágenes de 24x24 pixels y 8 bits de profundidad de color, y que crecerá en grupos de dos imágenes.

Esta función crea una lista vacía, en el punto siguiente veremos cómo añadir imágenes.

Una vez terminado el trabajo con las listas de imágenes procederemos a destruirlas, de modo que se liberen los recursos usados, para ello usaremos la función ImageList_Destroy:

        ImageList_Destroy(hIml);
        ImageList_Destroy(hIml2);

Añadir y eliminar imágenes

Disponemos de una batería de funciones para añadir, eliminar y sustituir imágenes en una lista de imágenes.

Las dos funciones principales para añadir imágenes a una lista son ImageList_Add y ImageList_AddMasked.

A pesar de lo que pueda indicar el nombre, las dos funciones pueden insertar imágenes con máscara, aunque la forma de indicar esa máscara es diferente en cada caso.

ImageList_Add toma tres parámetros. El primero es un manipulador de la lista de imágenes a la que se añadirá la imagen. El segundo es un manipulador de mapa de bits con la imagen a añadir. El tercero es un manipulador de mapa de bits con la máscará para la imagen. Si este parámetro es 0, no se usará máscara.

           hbmp = LoadBitmap(hInstance, "mapabits");
           ImageList_Add(hIml, hbmp, 0);
           DeleteObject(hbmp);
...
           hbmp2 = LoadBitmap(hInstance, "docmask");
           hbmp = LoadBitmap(hInstance, "doc1");
           ImageList_Add(hIml2, hbmp, hbmp2);
           DeleteObject(hbmp);
           DeleteObject(hbmp2);

ImageList_AddMasked también toma tres parámetros. El primero es un manipulador de la lista de imágenes a la que se añadirá la nueva imagen. El segundo es un manipulador de mapa de bits con la imagen a añadir. El tercero es un valor COLORREF con el color que se usará para generar la máscara. Cada pixel de ese color en la imagen insertada corresponde con un bit puesto a uno en la máscara.

           hbmp = LoadBitmap(hInstance, "doc1");
           ImageList_AddMasked(hIml2, hbmp, RGB(255,0,128));
           DeleteObject(hbmp);

La función ImageList_Replace nos permite sustituir una imagen de una lista de imágenes. Los parámetros son los mismos que para ImageList_Add, salvo que se añade un parámetro entero después del manipulador de la lista, que indica el índice de la imagen a sustituir.

También podemos añadir imágenes desde iconos, usando la macro ImageList_AddIcon o la función ImageList_ReplaceIcon. En el caso de la macro ImageList_AddIcon, el primer parámetro es un manipulador de la lista de imágenes a la que añadiremos la imagen, y el segundo un manipulador de icono.

           hicon = LoadIcon(hInstance, "tierra");
           ImageList_AddIcon(hIml2, hicon);

En el caso de la función ImageList_ReplaceIcon el primer argumento es el mismo, el segundo es un entero que indica el índice de la imagen a sustituir dento de la lista, y el tercero es un manipulador de icono. Si el segundo parámetro es -1, el icono se añade al final de la lista. Eso es lo que hace la macro ImageList_AddIcon.

Disponemos de una función similar para sustituir imágenes a partir de manipuladores de mapas de bits, en lugar de iconos, ImageList_Replace. El primer argumento es un manipulador de lista de imágenes, el segundo el índice de la imagen a sustituir, el tercero el manipulador del mapa de bits de la imagen, y el cuarto el manipulador del mapa de bits de la máscara.

           hicon = LoadIcon(hInstance, "casa");
           hbmp2 = LoadBitmap(hInstance, "docmask");
           hbmp = LoadBitmap(hInstance, "doc1");
           ImageList_Replace(hIml2, 9, hbmp, hbmp2);
           DeleteObject(hbmp2);
           DeleteObject(hbmp);
           ImageList_ReplaceIcon(hIml, 9, hicon);

Por último, también es posible eliminar imágenes de una lista mediante la función ImageList_Remove. Esta función sólo necesita dos argumentos. El primero, un manipulador de la lista de imágenes, y el segundo, el índice de la imagen a eliminar.

           ImageList_Remove(hIml2, 9);

Crear listas con imágenes

La función ImageList_LoadImage nos permite crear una lista de imágenes y añadir imágenes al mismo tiempo. Estos dos ejemplos son equivalentes:

           hIml = ImageList_LoadImage(hInstance, "mapabits", 32, 4, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);

Y:

           hIml = ImageList_Create(32, 32, ILC_COLOR24, 10, 4);
           hbmp = LoadBitmap(hInstance, "mapabits");
           ImageList_Add(hIml, hbmp, 0);
           DeleteObject(hbmp);

La función ImageList_LoadImage necesita siete parámetros. El primero es un manipulador de la instancia desde donde se carga el mapa de bits. También puede ser cero, si se cargan mapas de bits OEM (mapas de bits, iconos o cursores del sistema).

El segundo parámetro es un identificador de recurso, un nombre de fichero o un entero que identifique un recurso de sistema.

El tercero indica la anchura en pixels de cada imagen. El número de imágenes se calcula a partir de las dimensiones del mapa de bits.

El cuarto parámetro indica la cantidad de imágenes en que crecerá la lista si es necesario añadir imágenes.

El quinto es el color utilizado para crear la máscara, o CLR_NONE para si no se quiere generar máscara.

El sexto indica el tipo de imagen a cargar, IMAGE_BITMAP, IMAGE_ICON o IMAGE_CURSOR.

El séptimo parámetro es un conjunto de banderas que especifican el modo de cargar la imagen. En nuestro caso hemos especificado LR_CREATEDIBSECTION para indicar que se preserve el número de bits por color de la imagen original. Hay banderas para indicar que se cargue la imagen desde un fichero, para que sean transparentes, etc.

Otra función similar es ImageList_LoadBitmap, salvo que sólo dispone de los primeros cinco parámetros, por lo que no es posible cargar mapas de bits desde ficheros ni usar mapas de bits de tipo DIB.

           hIml = ImageList_LoadBitmap(hInstance, "mapabits", 32, 4, CLR_NONE);

La última función de este grupo es ImageList_Merge. Esta función crea una nueva lista de imágenes con una única imagen que es el resultado de mezclar dos imágenes procedentes de dos listas de imágenes. Las dos listas pueden ser la misma. El primer y segundo parámetro son el manipulador de la lista de imágenes e índice de la primera imagen, el tercero y cuarto el manipulador de la lista e índice de la segunda imagen. El quinto y sexto es un desplazamiento de la segunda imagen sobre la primera.

           hIml3 = ImageList_Merge(hIml2, 2, hIml2, 10, 0, 0);

Obtener iconos

La función ImageList_GetIcon nos permite extraer un icono desde una lista de imágenes. El primer parámetro es el manipulador de una lista de imágenes, el segundo parámetro es el índice de la imagen. El tercero es una combinación de banderas análogas a las de la función ImageList_Draw. El resultado es el manipulador de un icono extraido de la lista.

	HICON hicon;
	...
	hicon = ImageList_GetIcon(himl, 2, ILD_TRANSPARENT);

Mostrar imágenes

Todo lo anterior suele ser suficiente para muchas de las aplicaciones de las listas de imágenes. Por ejemplo, los controles ListView y TreeView se encargan de mostrar las imágenes para cada ítem, según su estado de forma automática. Sin embargo, también podemos mostrar imágenes de una lista de imágenes desde nuestros programas, para ello disponemos de un par de funciones.

La función ImageList_Draw permite trazar una imagen de una lista de imágenes. Esta función requiere seis parámetros. El primero, un manipulador de la lista de imágenes. El segundo, un índice de la imagen a mostrar. El tercero, un manipulador de contexto de dispositivo de destino. El cuarto y quinto, las coordenadas donde se mostrará la imagen. Y el sexto el estilo de trazado.

Hay que tener en cuenta que si la lista de imágenes ha sido creada con la bandera ILC_COLORDDB, ILC_COLOR24 o ILC_COLOR32, los estilos resaltados ILD_BLEND25 e ILD_BLEND50 usarán una trama de puntos, y no se podrá distinguir entre ellos. El resto de las banderas de color usan una mezcla para hacer los resaltados, y en general, tendrán un aspecto diferente si se usa ILD_BLEND25 o ILD_BLEND50.

Los estilo ILD_BLEND25 e ILD_BLEND50 se añaden a ILD_NORMAL y ILD_TRANSPARENT para indicar que el usuario ha seleccionado la imagen.

Además, el modo normal con imágenes de listas sin máscara es equivalente al modo transparente, en ninguno de los dos casos hay transparencias.

Cuando estos estilos se aplican a imágenes que provienen de listas con máscaras, la diferencia es más sutil. Si se muestra en el estilo normal la parte del fondo enmascarada se muestra con el color de fondo de la lista de imágenes. Si se muestra en el estilo transparente la parte del fondo enmascarada conserva el contenido original.

Por último, el estilo ILD_MASK muestra la máscara, si existe.

	ImageList_Draw(hIml, 3, hDC, 10, 40, ILD_BLEND25 | ILD_NORMAL);

La función ImageList_DrawEx es más versatil. Además de los parámetros usados con ImageList_Draw, se deben especificar algunos más, lo que nos proporciona un control adicional sobre el aspecto de las imágenes.

Los cinco primeros parámetros son los mismos que para ImageList_Draw. Los dos siguientes indican la anchura y altura de la imagen a mostrar, pueden ser cero, para indicar que se muestre la anchura y altura por defecto. El octavo parámetro es un color usado para pintar el fondo. Puede ser cualquier color RGB, el valor CLR_NONE, para indicar que no hay color de fondo o CLR_DEFAULT para indicar que se use el color de fondo por defecto. El noveno parámetro es un color de usado para el primer plano, y también puede tomar los valores CLR_NONE o CLR_DEFAULT. Este cólor sólo se usa para los resaltados ILD_BLEND25 y ILD_BLEND50, y no se tiene en cuenta si no aparencen esas banderas. Si se usa el valor CLR_DEFAULT, el resultado es el mismo que con la función ImageList_Draw. El último parámetro es el mismo que el último de ImageList_Draw, e indica las banderas de estilo.

	ImageList_DrawEx(hIml, 3, hDC, 10, 40, 32, 32, GetSysColor(COLOR_MENU), RGB(255,0,0) , ILD_BLEND25 | ILD_NORMAL);

El color de fondo

Para cada lista de imágenes podemos establecer un color de fondo, que se usará para rellenar el espacio no enmascarado cuando se tracen imágenes en el estilo normal. Este color no afecta a listas de imágenes sin máscaras. Para asignar el color de fondo se usa la función ImageList_SetBkColor, indicando como parámetros el manipulador de la lista de imágenes y el color de fondo que se quiere establecer. Para recuperar el color de fondo de una lista de imágenes podemos usar la función ImageList_GetBkColor.

	ImageList_SetBkColor(hIml, GetSysColor(COLOR_MENU));

Imágenes superpuestas

Acceso directo

Acceso directo

En cada lista de imágenes se pueden indicar hasta cuatro imágenes que se podrán usar posteriormente como imágenes superpuestas. El efecto es similar al que usa Windows para crear los iconos de acceso directo, cuando añade una pequeña flecha en una de las esquinas al icono de la aplicación.

Estas imágenes se conocen en el API como "overlay masks". Se puede usar cualquier imagen de la lista como imagen superpuesta, tan sólo hay que especificar cuales de ellas lo pueden ser usando la función ImageList_SetOverlayImage. Hay que especificar tres parámetros. El primero un manipulador de la lista de imágenes, el segundo el índice de la imagen, y el tercero el número de la imagen superpuesta, entre 1 y 4.

Para trazar una imagen superpuesta se usan las funciones ImageList_Draw o ImageList_DrawEx, el índice de la imagen superpuesta se indica mediante la macro INDEXTOOVERLAYMASK añadida a las banderas de estilo.

	ImageList_SetOverlayImage(hIml2, 6, 1);
...
	ImageList_Draw(hIml2, 2, hDC, 10, 10, ILD_TRANSPARENT | INDEXTOOVERLAYMASK(1));

Ejemplo 81

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 81 win081.zip 2012-05-16 278884 bytes 71

Arrastre de imágenes

Las operaciones de arrastrar y soltar siguen siempre un patrón parecido. Empiezan con una pulsación de ratón sobre el objeto a arrastrar. Mientras se mantiene pulsado, el objeto seleccionado se mueve junto con el cursor del ratón. Cuando se suelta el botón del ratón, el objeto se suelta en el punto actual del cursor.

Generalmente usaremos el botón izquierdo del ratón, eso quiere decir que deberemos procesar tres mensajes: WM_LBUTTONDOWN, WM_MOUSEMOVE y WM_LBUTTONUP.

Inicio del arrastre

Cuando detectemos la pulsación del botón del ratón, comprobaremos si el cursor está sobre el objeto a arrastrar. Este proceso puede ser muy complejo si los posibles objetos a arrastrar son muchos. La técnica común es crear un rectángulo o una región para cada objeto, y comprobar si las coordenadas del cursor están en cada uno de esos rectángulos o regiones, usando las funciones PtInRect o PtInRegion.

Una vez localizado el objeto seleccionado entramos en el modo de arrastre. Esto implica algunas acciones, como capturar el ratón para la ventana actual, usando SetCapture, opcionalmente, ocultar el cursor con ShowCursor, ya que el objeto arrastrado puede hacer su función, y borrar el objeto de su posición inicial, ya que ahora deberá desplazarse sobre la ventana a la vez que el ratón. También necesitamos una variable estática que nos indique que estamos en una operación de arrastrar y soltar.

Finalmente tenemos que usar dos funciones específicas cuando se arrastran imágenes pertenecientes a listas de imágenes. ImageList_BeginDrag y ImageList_DragEnter.

La primera función, ImageList_BeginDrag, crea una lista de imágenes temporal que se usa para mostrar la imagen a arrastrar. Necesita cuatro parámetros: el primero es un manipulador de la lista de imágenes que contiene la imagen a arrastrar, el segundo el índice dentro de la lista que identifica la imagen. El tercero y cuarto indican las coordendas del punto caliente. Este punto es análogo al punto activo de un cursor. De hecho, la imagen arrastrada se comporta de forma similar a cómo lo hace un cursor del ratón. Las coordenadas del punto de acceso son relativas a la esquina superior izquierda de la imagen, por lo que tendremos que hacer ciertos cálculos a partir de las coordenadas del ratón y las de la esquina superior izquierda de la imagen, concretamente, esos cálculos son una resta.

La segunda función, ImageList_DragEnter, se encarga de bloquear las actualizaciones de la ventana especificada durante una operación de arrastre y además muestra la imagen arrastrada en la posición especificada dentro de la ventana. Esta función requiere tres parámetros: el primero es un manipulador de la ventana en la que se va a realizar la operación de arrastre, el segundo y tercer parámetros son las coordenadas en las que se muestra la imagen, pero hay que tener cuidado, ya que esas coordenadas son relativas a la esquina superior izquierda de la ventana, y no del área de cliente. Necesitaremos, pues, el desplazamiento del área de cliente con respecto a la ventana. Podemos hacerlo mediante estas llamadas:

    static int cxBorde;
    static int cyBorde;
    RECT rv;
    POINT p;
...
           GetWindowRect(hwnd, &rv);
           p.x=p.y=0;
           ClientToScreen(hwnd, &p);
           cxBorde = p.x-rv.left;
           cyBorde = p.y-rv.top;

Primero obtenemos las coordenadas de la ventana en el rectángulo rv. El rectángulo contendrá las equinas de la ventana en coordenadas de pantalla. Iniciamos el punto p con las coordenadas 0,0, y traducimos esas coordenadas de cliente a pantalla. El desplazamiento del área de cliente sobre la esquina superior izquierda de la ventana es la diferencia entre las coordenadas del punto y las de la esquina superior izquierda en rv.

El tratamiento del mensaje WM_LBUTTONDOWN queda así:

        case WM_LBUTTONDOWN:
           ptCur.x = LOWORD(lParam);
           ptCur.y = HIWORD(lParam);
           if(!PtInRect(&re, ptCur)) return FALSE;
           // Capturar el ratón
           SetCapture(hwnd);
           endrag = TRUE;
           ShowCursor(FALSE);
           // Borrar la imagen a arrastrar:
           InvalidateRect(hwnd, &re, TRUE);
           UpdateWindow(hwnd);
           // Calcular el hotspot:
           hotspot.x = ptCur.x-re.left;
           hotspot.y = ptCur.y-re.top;
           ImageList_BeginDrag(hIml, 2, hotspot.x, hotspot.y);
           ImageList_DragEnter(hwnd, ptCur.x + cxBorde, ptCur.y + cyBorde);
           break;

Arrastre

Durante el arrastre mantendremos pulsado el botón del ratón, y procesaremos el mensaje WM_MOUSEMOVE. Cada vez que lo procesemos invocaremos a la función ImageList_DragMove, al que pasaremos las coordenadas de ventana de la posición de la imagen. De nuevo, las coordenadas del ratón que obtenemos del mensaje son coordenadas de cliente, por lo que habrá que añadir el desplazamiento del área de cliente.

        case WM_MOUSEMOVE:
           if(!endrag) return TRUE;
           ptCur.x = LOWORD(lParam);
           ptCur.y = HIWORD(lParam);
           ImageList_DragMove(ptCur.x + cxBorde, ptCur.y + cyBorde);
           break;

Final del arrastre

Finalmente, en algún momento finalizaremos la operación de arrastrar y soltar, soltando el botón del ratón. Para ello procesaremos el mensaje WM_LBUTTONUP.

Lo primero es terminar la operación de arrastre, invocando a la función ImageList_EndDrag, que no necesita argumentos. A continuación invocaremos a la función ImageList_DragLeave, usando como argumento un manipulador de la ventana. Esta función vuelve a permitir las actualizaciones en la ventana, borra la imagen arrastrada, y también la lista de imágenes temporal creada para la operación de arrastre.

Sólo nos queda volver a hacer visible el cursor, si lo habíamos ocultado, dibujaremos la imagen en la posición final, y liberamos el ratón.

Finalmente, actualizamos el rectángulo con la nueva posición de la imagen, de modo que podamos volver a arrastrarla.

        case WM_LBUTTONUP:
           if(!endrag) return TRUE;
           ptCur.x = LOWORD(lParam);
           ptCur.y = HIWORD(lParam);
           ImageList_EndDrag();
           ImageList_DragLeave(hwnd);

           ShowCursor(TRUE);
           endrag = FALSE;

           DibujarImagen(hwnd, hIml, 2, ptCur.x - hotspot.x, ptCur.y - hotspot.y);

           ReleaseCapture();
           re.left = ptCur.x - hotspot.x;
           re.top = ptCur.y - hotspot.y;
           re.right = re.left+32;
           re.bottom = re.top+32;
           return TRUE;

Hay otras dos funciones relacionadas con el arrastre de imágenes. ImageList_SetDragCursorImage crea una nueva imagen de arrastre mediante la combinación de la imagen especificada, generalmente la imagen de un cursor del ratón, y de la imagen actualmente arrastrada. Si se usa esta imagen generada es imprescindible ocultar el cursor, ya que de otro modo se mostrarán dos cursores, lo que siempre es poco conveniente.

Ya hemos mencionado que ImageList_BeginDrag crea una lista de imágenes temporal, la función ImageList_GetDragImage recupera la imagen temporal, además de su posición actual y el desplazamento del punto activo.

Ejemplo 82

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 82 win082.zip 2012-06-15 15651 bytes 64

Información de imagen

Disponemos de dos funciones para obtener información sobre listas de imágenes. La primera es ImageList_GetImageInfo, que obtiene algunos datos sobre una imagen dentro de una lista de imágenes. Se necesitan tres parámetros, el primero es un manipulador de una lista de imágenes, el segundo el índice de la imagen sobre la que queremos información, y el tercero un puntero a una estructura IMAGEINFO en la que se almacenarán los datos sobre la imagen. Esta estructura tiene cinco campos, aunque dos de ellos no se usan:

  • hbmImage: contiene un manipulador del mapa de bits que contiene las imágenes de la lista de imágenes.
  • hbmMask: contiene un manipulador del mapa de bits monocromo que contiene la máscara de las imágenes de la lista de imágenes.
  • rcImage: rectángulo que bordea la imagen indicada en el índice dentro del mapa de bits.

La otra función es ImageList_GetImageCount que devuelve el número de imágenes que contiene la lista de imágenes cuyo manipulador se ha pasado como parámetro.