Capítulo 53 Control de cabecera

Ejemplo de control de cabecera

Un control de cabecera es una ventana estrecha, en forma de barra, dividida en zonas, a las que denominamos ítems. Generalmente se usan con listas formadas por varias columnas, a cada una de las cuales le corresponde un ítem. El usuario puede modificar la anchura de cada ítem arrastrando el divisor que se encuentra entre los ítems.

Los controles de cabecera no están pensados para ser usados como controles individuales, sino como controles hijos de otros controles, como listas. Sin embargo, asociar un control de cabecera a un control lista no funcionará como esperaríamos directamente, ya que los controles lista no están preparados para mostrar información en varias columnas. En esos casos deberemos crear una subclase del control lista, y además, la lista deberá ser owner-draw.

Sin embargo, hay otros controles comunes que incluyen un control de cabecera, como el ListView, que aún no hemos visto. Lo que aprendamos sobre controles de cabecera nos servirá para aplicarlo a estos controles, ya que podremos conseguir un manipulador al control de cabecera asociado a ellos.

Creación de un control de cabecera

Crear un control de cabecera requiere varios pasos. Para empezar, usaremos la función CreateWindowEx, especificando el valor WC_HEADER como clase de ventana de control. Es imprescindible indicar al menos el estilo WS_CHILD, además de los específicos para estos controles que consideremos necesarios en nuestro caso.

        CreateWindowEx(0, WC_HEADER, NULL,
            WS_CHILD | HDS_BUTTONS | HDS_HORZ,
            0, 0, 0, 0,
            hwnd, (HMENU)ID_HEADER1,
            hInstance, NULL);

No necesitamos especificar una posición ni las dimensiones del control, ya que a continuación las calcularemos en función de la ventana padre y moveremos el control a esa posición.

Para calcular la posición y dimensiones del control usaremos el mensaje HDM_LAYOUT o la macro Header_Layout, ambos son equivalentes. Antes, iniciaremos los campos prc y pwpos de una estructura HDLAYOUT.

El campo prc debe contener un puntero a una estructura RECT con las dimensiones del área de cliente de la ventana o control padre del control de cabecera.

El campo pwpos debe contener un puntero a una estructura WINDOWPOS, que recibirá las coordendas de la posición y las dimensiones que debe tener el control de cabecera, según el tamaño y posición del rectángulo indicado.

    RECT rc;
    HDLAYOUT hdl;
    WINDOWPOS wp;
...
        GetClientRect(hwnd, &rc);
        hdl.prc = &rc;
        hdl.pwpos = ℘
        SendDlgItemMessage(hwnd, ID_HEADER1, HDM_LAYOUT, 0, (LPARAM)&hdl);
        /* O bien la macro:
        Header_Layout(GetDlgItem(hwnd, ID_HEADER1), &hdl);
        */

Una vez tenemos las dimensiones y coordenadas del control, sólo queda moverlo a esa posición y modificar su tamaño. Para ello usaremos la función SetWindowPos, y aprovecharemos para hacerlo visible, añadiendo la bandera SWP_SHOWWINDOW:

        SetWindowPos(GetDlgItem(hwnd, ID_HEADER1), wp.hwndInsertAfter, wp.x, wp.y,
            wp.cx, wp.cy, wp.flags | SWP_SHOWWINDOW);

Como con el resto de los controles comunes, es necesario asegurarse de que la DLL ha sido cargada mediante una llamada a la función InitCommonControls.

La apariencia de este tipo de controles también se ve afectada si se activan o no los estilos visuales.

Decordemos que para activar los estilos visuales hay que incluir un fichero de manifiesto en el fichero de recursos.

Añadir columnas

Para insertar columnas en un control de cabecera se usa el mensaje HDM_INSERTITEM, indicando en el parámetro wParam el índice de la columna a continuación de la cual se insertará la nueva, y el lParam un puntero a una estructura HDITEM.

En la estructura HDITEM el parámetro mask indica qué miembros opcionales de la estructura contienen valores válidos.

Cuando se trate de columnas con texto, deberemos indicar los valores de pszText y cchTextMax, con los valores del texto y su longitud máxima, respectivamente.

cxy indica la anchura o altura de la columna o fila, dependiendo de si el control es horizontal o vertical.

También se puede usar un mapa de bits, mediante el miembro hbm, o bien una imagen dentro de una lista de imágenes indicando el índice en el miembro iImage.

El miembro lParam permite usar un dato asociado al ítem, definido por la aplicación.

iOrder sirve para indicar el orden en que se mostrará la columna dentro del control. Esto puede ser útil cuando se quieren conservar las preferencias de orden del usuario, en lugar de creaer el control siempre con el orden original por defecto.

El puntero pvFilter se usa para asignar un filtro a la columna. El tipo de filtro se indica el en miembro type, que puede tomar los valores HDFT_ISSTRING para cadena, HDFT_ISNUMBER para valores enteros, HDFT_ISDATE para fechas y HDFT_HASNOVALUE para ignorar el filtro.

Cuando se define un tipo concreto el sistema impide que se introduzcan valores no permitidos, por ejemplo, no será posible introducir texto en un filtro de tipo HDFT_ISNUMBER o HDFT_ISDATE.

El miembro state indica el estado de la columna, el valor HDIS_FOCUSED indica que la columna tiene el foco del teclado.

El miembro fmt sirve para determinar el formato: si se trata de imágenes o texto, y en ese caso, si el texto se muestra centrado, a la derecha, a la izquierda o de derecha a izquierda. Se puede añadir una imagen de flecha arriba o flecha abajo, para indicar el orden en que aparecen los elementos de la columna mediante los valores HDF_SORTUP o HDF_SORTDOWN, respectivamente. El valor HDF_CHECKBOX indica que se debe mostrar un checkbox, y si se indica además el valor HDF_CHECKED se mostrará marcado. HDF_FIXEDWIDTH impide que el usuario pueda modificar la anchura de la columna, HDF_SPLITBUTTON muestra un botón de despliegue.

int InsertarItem(HWND hwndHeader, int iDespuesDe, int nAncho, LPTSTR lpsz)
{
    HDITEM hdi;
    int index;

    hdi.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH;
    hdi.cxy = nAncho;
    hdi.pszText = lpsz;
    hdi.cchTextMax = strlen(hdi.pszText)/sizeof(hdi.pszText[0]);
    hdi.fmt = HDF_LEFT | HDF_STRING;

    index = SendMessage(hwndHeader, HDM_INSERTITEM,
        (WPARAM) iDespuesDe, (LPARAM) &:hdi);

    return index;
}

Cambio de tamaño de la ventana padre

Cada vez que el tamaño de la ventana padre del control cambie, deberemos ajustar el tamaño y posición del control. En esos casos recibiremos un mensaje WM_SIZE, y deberemos repetir los pasos anteriores:

    case WM_SIZE:
        GetClientRect(hwnd, &rc);
        hdl.prc = &rc;
        hdl.pwpos = ℘
        SendDlgItemMessage(hwnd, ID_HEADER1, HDM_LAYOUT, 0, (LPARAM)&hdl);
        SetWindowPos(GetDlgItem(hwnd, ID_HEADER1), wp.hwndInsertAfter, wp.x, wp.y,
            wp.cx, wp.cy, wp.flags | SWP_SHOWWINDOW);

Estilos

Hay varios estilos que se pueden aplicar a los controles de cabecera.

HDS_BUTTONS: cuando se requiera que la aplicación realice altuna tarea (como seleccionar o copiar) cuando el usuario pulsa sobre alguna de las columnas, este estilo hace que cada encabezado se comporte como un botón. Si no se usa, cada ítem se comporta como una etiqueta estática.

HDS_DRAGDROP: permite arrastrar los elementos del encabezado.

HDS_FILTERBAR: añade una segunda barra con filtros.

HDS_FLAT: produce una apariencia plana.

HDS_FULLDRAG: para que se siga mostrando el contenido de la columna mientras el usuario cambia su tamaño. Si no se indica sólo se muestra el borde hasta que el usuario establece el nuevo tamaño.

HDS_HIDDEN: Indica un control de encabezado que se va a ocultar. Este estilo no oculta el control. En su lugar, cuando se envía el mensaje de HDM_LAYOUT a un control de encabezado con el estilo de HDS_HIDDEN, el control devuelve cero en el miembro CY de la estructura windowpos ( . A continuación, ocultaría el control estableciendo su alto en cero. Esto puede ser útil si desea usar el control como contenedor de información en lugar de como un control visual.

HDS_HORZ: Crea un control de encabezado con una orientación horizontal. Hasta donde he podido ver, esta es la opción por defecto, y no es posible crear controles de encabezado verticales.

HDS_HOTTRACK: Habilita el seguimiento activo. Este estilo no parece tener ninguna funcionalidad en controles de cabecera, pero sí se usa en controles ListView, que contienen en su composición un control de cabecera.

HDS_CHECKBOXES: Permite colocar cajas de chequeo en los elementos de encabezado.

HDS_NOSIZING: El usuario no puede modificar la anchura de los items arrastrando el divisor.

HDS_OVERFLOW: Se muestra un botón cuando no se pueden mostrar todos los elementos dentro del rectángulo del control de encabezado. Al hacer clic en este botón, se envía una notificación HDN_OVERFLOWCLICK.

Mensajes de gestión de columnas

Además del mensaje HDM_INSERTITEM que vimos antes, también disponemos del mensaje HDM_GETITEMCOUNT que obtiene el número de columnas actualmente en el control indicado.

int n;
n = SendDlgItemMessage(hwnd, ID_HEADER1, HDM_GETITEMCOUNT, 0, 0);

El mensaje HDM_DELETEITEM permite eliminar una columna, en wParam se debe indicar el índice de la columna a eliminar.

SendDlgItemMessage(hwnd, ID_HEADER1, HDM_DELETEITEM, (WPARAM)2, 0);

El mensaje HDM_SETITEM permite modificar una columna existente. En wParam indicaremos el índice de la columna a modificar y en lParam un puntero a una estructura HDITEM con los nuevos valores de la columna. Por lo demás funciona igual que HDM_INSERTITEM.

En este ejemplo modificamos el formato de la segunda columna (los índices empiezan en 0):

    HDITEM hdi;
    hdi.mask = HDI_FORMAT;
    hdi.fmt = HDF_STRING | HDF_CHECKBOX | HDF_CENTER | HDF_SORTDOWN | HDF_SPLITBUTTON;

    SendDlgItemMessage(hwnd, ID_HEADER1, HDM_SETITEM, (WPARAM)1, (LPARAM)&hdi);

Por último, el mensaje HDM_GETITEM permite recuperar la información de un ítem de columna. En wParam se indica el índice del ítem a recuperar y en lParam un puntero a una estructura HD_ITEM o HDITEM en el que se devolverá la información. En el miembro mask indicaremos qué valores queremos recuperar.

  HDITEM hdi;
  hdi.mask = HDI_FILTER; // Recuperar información del filtro
  SendDlgItemMessage(hwnd, ID_HEADER1, HDM_GETITEM, (WPARAM)1, (LPARAM)&hdi);

Mensajes relacionados con el orden de columnas

Dado que las columnas se pueden mover usando operaciones de arrastre, o mediante mensajes, el índice de cada una no tiene por qué corresponder con su posición dentro del control de cabecera. Existen varios mensajes para relacionar índices y posiciones de columnas.

El mensaje HDM_GETORDERARRAY obtiene un array de índices de columnas en el orden en que aparecen en el control de izquierda a derecha. En lParam se pasa un puntero a un array de enteros que recibirán los índices, y en wParam se indica el valor de valores a recuperar, que como máximo será el número de columnas.

    int *orden;
    int n;
    n = SendDlgItemMessage(hwnd, ID_HEADER1, HDM_GETITEMCOUNT, 0, 0);
    if((orden = (int*)calloc(n, sizeof(int)))) {
        SendDlgItemMessage(hwnd, ID_HEADER1, HDM_GETORDERARRAY, (WPARAM)n, (LPARAM)orden);
        free(orden);
    }

De forma análoga, podemos definir un nuevo orden para las columnas usado el mensaje HDM_SETORDERARRAY. Los parámetros tiene el mismo significado que en el mensaje anterior.

    int *orden;
    int n;
    n = SendDlgItemMessage(hwnd, ID_HEADER1, HDM_GETITEMCOUNT, 0, 0);
    if((orden = (int*)calloc(n, sizeof(int)))) {
        orden[0] = 2;
        orden[1] = 1;
        orden[2] = 0;
        SendDlgItemMessage(hwnd, ID_HEADER1, HDM_SETORDERARRAY, (WPARAM)n, (LPARAM)orden);
        free(orden);
    }

El mensaje HDM_ORDERTOINDEX obtiene el índice de una columna a partir de su posición de izquierda a derecha. En wParam se indica el valor del orden, y lParam no se usa.

  int i = SendDlgItemMessage(hwnd, ID_HEADER1, HDM_ORDERTOINDEX, (WPARAM)1, 0);

El mensaje HDM_HITTEST sirve para verificar si un punto determinado de la pantalla pertenece a un control de cabecera o a un ítem concreto de un control de cabecera, y en ese caso a cual.

El punto se porporciona en el miembro pt, de tipo POINT dentro de una estructura HDHITTESTINFO cuya dirección se pasa en el parámetro lParam. Se retorna el índice del índice de la columna que contiene el punto, o -1 si no pertenece a ninguna. También se retornan el resto de los miembros de la estructura HDHITTESTINFO actualizados. El miembro flags contiene información adicional sobre la posición relativa del punto con respecto al control o al ítem.

    hhi.pt.x=10;
    hhi.pt.y=10;
    int i;
    i = SendDlgItemMessage(hwnd, ID_HEADER1, HDM_HITTEST, (WPARAM)0, (LPARAM)&hhi);

Mensajes de arrastre de items

Los controles de cabecera permiten operaciones de arrastre de columnas. Estas operaciones son automáticas, tan sólo es necesario que el control tenga definido el estilo HDS_DRAGDROP.

Cuando el usuario inicie una operación de arrastre, la aplicación recibirá un mensaje de notificación HDN_BEGINDRAG. Si la aplicación retorna FALSE, el sistema se ocupará de crear la imagen y mostrarla en pantalla a medida que el puntro del ratón cambie de posición. Si por el contrario queremos que la aplicación se ocupe de todas las tareas: crear una imagen y actualizar la pantalla cuando el ratón se mueva, tendremos que realizar esas tareas y retornar TRUE.

En caso de que queramos evitar el comportamiento automático, mediante el mensaje HDM_CREATEDRAGIMAGE podemos obtener una imagen semitransparente, para ello pasaremos el índice del ítem en wParam, y se retornará una lista de imágenes con una única imagen.

Cuando la operación de arrastre concluya, es decir, cuando el usuario libere el botón izquierdo del ratón, se enviará un menaje de notificación HDN_ENDDRAG a la aplicación. De nuevo, si la aplicación retorna FALSE a este mensaje, el sistema se encargará de situar el ítem en su nueva posición. En caso contrario será la aplicación la encargada de reordenar los ítems, usando el mensaje HDM_SETITEM o el mensaje HDM_SETORDERARRAY.

De hecho, si optamos por el funcionamiento automático no será necesario manejar estos mensajes de notificación. El comportamiento por defecto es retornar FALSE en ambos.

Control de cabecera divisor

En el comportamiento automático se resalta con una linea azul la separación entre items donde se insertará el ítem que se está arrastrando. Esto se puede hacer también usando el mensaje HDM_SETHOTDIVIDER cuando sea la aplicación la responsable de procesar los mensajes de draganddrop de items.

Arrastre de divisores

Si el control de cabecera no tiene el estilo HDS_NOSIZING, el usuario podrá modificar la anchura de un item arrastrando el divisor de la derecha de ese item.

Windows gestiona automáticamente las operaciones de arrastre de divisores, pero si una aplicación tiene que gestionar estas operaciones tendrá que procesar algunos mensajes de notificación.

La operación de arrastre comienza cuando el usuario hace clic con el botón izquierdo sobre la división entre dos items. El programa indica que el cursor está en ese divisor cambiado el cursor Cursor de divisor. La aplicación recibe un mensaje de notificación HDN_BEGINTRACK. Se siguen enviando mensajes de notificación HDN_TRACK mientras el usuario mantenga pulsado el botón izquierdo y mueva el ratón. Cuando se libere el botón del ratón se envía un mensaje de notificación HDN_ENDTRACK.

Además la aplicación recibirá un mensaje de notificación HDN_DIVIDERDBLCLICK cuando el usuario haga doble clic sobre un divisor. De nuevo, no hay un comportamiento automático como respuesta a este mensaje y será la aplicación la responsable de procesarlo. El comportamiento esperado en este caso es ajustar la anchura del item a la mínima para mostrar el texto, pero en cada caso esto puede ser diferente.

Mensajes de filtros

Si el control de cabecera tiene el estilo HDS_FILTERBAR se mostrará una segunda línea en cada item del control que permite editar textos, números o fechas que se usarán como filtro para la columna.

Cuando se especifica como numérico para el tipo de filtro, sólo se permiten valores enteros, no en coma flotante.

Disponemos de varios mensajes para actualizar filtros, HDM_CLEARFILTER borra el valor del filtro para un ítem determinado. El índice del item se especifica en wParam. lParam no se usa.

El mensaje HDM_EDITFILTER sirve para situal el foco en el cuadro de edición del filtro. En wParam se indica el índice del item, y en lParam se indica qué hacer con el valor del texto si el filtro ya estaba siendo editado por el usuario.

Desde que se modifica el valor de un filtro hasta que se envía a la aplicación un mensaje de notificación HDN_FILTERCHANGE transcurre cierto tiempo. Este tiempo se puede modificar mediante el mensaje HDM_SETFILTERCHANGETIMEOUT indicando en lParam el tiempo en milisegundos desde que se modifican los atributos del filtro hasta que se envía la notificación.

Cada vez que el valor de un filtro se modifica se envía un mensaje de notificación HDN_FILTERCHANGE a la aplicación. Esto se hace incluso durante la edición del valor del filtro, siempre que no haya transcurrido el tiempo de espera entre modificaciones sucesivas.

Por ejemplo, el usuario está editando un filtro, y el tiempo de espera (timeout) está establecido en un segundo (1000 ms), mientras el usuario está escribiendo, si deja de hacerlo más de un segundo, se enviará un mensaje de notificación HDN_FILTERCHANGE, si no llega a pasar ese tiempo entre actualizaciones del valor, no se enviarán mensajes de notificación.

Hay que tener en cuenta que aplicar un filtro puede ser una tarea que requiera mucho tiempo de procesador y de actualización de pantalla, por lo que especificar un tiempo de espera permite que el usuario pueda modificar el valor del filtro sin que la aplicación reciba la notificación de que el valor del filtro ha sido modificado. Estos mensajes de notificación se envían durante la edición del filtro, y no sólo cuando el usuario de por terminada la edición pulsado return, o haciendo click en otra zona de la pantalla.

También se envían a la aplicación mensajes de notificación cuando se inicia la edición de un filtro, HDN_BEGINFILTEREDIT, y cuando termina HDN_ENDFILTEREDIT.

Por último, el mensaje de notificación HDN_FILTERBTNCLICK se envía a la aplicación cuando se pulsa sobre el icono del filtro de un item (el icono del embudo). También se envía esta notificación como respuesta a un mensaje HDM_SETITEM.

De nuevo, el sistema no tiene un comportamiento definido para este botón, y será la aplicación la encargada de responder adecuadamente a éste mensaje, si lo considera necesario.

Indicativos de orden

Cuando un control de cabecera esté asociado a un listview, cada columna contendrá una lista de valores. Ya hemos visto que podemos filtrar esos valores, y además podremos necesitar ordenarlos. Cuando los valores de una columna estén ordenados pueden aparecer en orden ascendente o descendente. Cada item permite mostrar una marca para indicar el orden. Estas marcas se activan mediante el miembro fmt de la estructura HDITEM, añadiendo los valores HDF_SORTUP o HDF_SORTDOWN, respectivamente.

Podemos hace esto al insertar cada item, o bien modificándolo mediante un mensaje HDM_SETITEM

    HDITEM hdi;
    int iItem = 1;
...
    hdi.mask = HDI_FORMAT;
    SendDlgItemMessage(hwnd, ID_HEADER1, HDM_GETITEM, (WPARAM)iItem, (LPARAM)&hdi);
    hdi.fmt |= HDF_SORTUP;
    SendDlgItemMessage(hwnd, ID_HEADER1, HDM_SETITEM, (WPARAM)iItem, (LPARAM)&hdi);
  

Mensajes de foco de teclado

Una de las columnas del control de cabecera tendrá el foco del teclado, que se visualizará resaltando el fondo del item. Podemos recuperar el número del item que tiene el foco actualmente usando el mensaje HDM_GETFOCUSEDITEM. Este mensaje no requiere parámetros. También podemos asignar el foco al item que queramos mediante el mensaje HDM_SETFOCUSEDITEM indicando el item en el parámetro lParam.

Mensajes de situación en ventana

El mensaje HDM_LAYOUT calcula las dimensiones y posición correctas para un control de cabecera a partir de un rectángulo determinado. El parámetro wParam no se usa y en lParam se pasa un puntero a una estructura HDLAYOUT. Al enviar este mensaje, el miembro prc debe contener las coordenadas del rectángulo, y devolverá en el miembro pwpos el tamaño y posición calculados para el control.

El mensaje HDM_GETITEMRECT sirve para recuperar el rectángulo que ocupa un determinado item del control. En wParam se debe indicar el índice del item y en lParam un puntero a una estructura RECT que recibirá la información del rectángulo.

Botón de desplegar

Control de cabecera dropdown

Cuando se crea un control de cabecera con el estilo HDS_BUTTONS y el ítem incluya la bandera de formato HDF_SPLITBUTTON, la aplicación recibirá un mensaje de notificación HDN_DROPDOWN cuando el usuario haga clic con el ratón en el icono de despliegue. El icono permanecerá oculto, y sólo se mostrará cuando el cursor del ratón esté sobre el ítem.

No hay comportamiento por defecto definido para este mensaje, será el programador el responsable de mostrar en pantalla el resultado de pulsar ese botón. Por ejemplo, las hojas de cálculo despliegan una ventana para definir filtros, pero cada aplicación puede requerir un comportamiento diferente.

Existen dos mensajes que pueden resultar útiles para procesar éste mensaje:

  • HDM_GETITEMDROPDOWNRECT obtiene un rectángulo en el que se muestra el icono de despliegue. En wParam se indica el índice del ítem, y en lParam se pasa un puntero a una estructura RECT que recibirá las coordenadas del rectángulo.
  • HDM_GETITEMRECT obtiene las coordenadas del rectángulo que contiene el ítem. En wParam se indica el índice del ítem, y en lParam se pasa un puntero a una estructura RECT que recibirá las coordenadas del rectángulo.

Cajas de chequeo

Para cada item se puede añadir una caja de chequeo. Para ello el control debe tener el estilo HDS_CHECKBOXES, y además, el item debe tener el flag HDF_CHECKBOX. Adicionalmente se puede añadir el flag HDF_CHECKED para indicar que la caja esté marcada.

Se enviará un mensaje de notificación HDN_ITEMSTATEICONCLICK a la aplicación cuando el usuario pulse sobre la caja de chequeo. Para que este mensaje se envíe, el control también debe tener el estilo HDS_BUTTONS.

        case HDN_ITEMSTATEICONCLICK:
            iItem = pnmhdr->iItem;
            hdi.mask = HDI_FORMAT;
            SendDlgItemMessage(hwnd, ID_HEADER1, HDM_GETITEM, (WPARAM)iItem, (LPARAM)&hdi);
            if(hdi.fmt & HDF_CHECKED) hdi.fmt = (UINT)hdi.fmt & ~HDF_CHECKED;
            else hdi.fmt |= HDF_CHECKED;
            hdi.mask = HDI_FORMAT;
            SendDlgItemMessage(hwnd, ID_HEADER1, HDM_SETITEM, (WPARAM)iItem, (LPARAM)&hdi);
            break;

Overflow

Si se crea el control de cabecera con el estilo HDS_OVERFLOW, si todos los items no pueden ser visualizados en pantalla porque el ancho de la ventana es insuficiente, se mostrará un botón Botón de overflow a la derecha del control.

Podemos recuperar el rectángulo delimitador de ese botón mediante el mensaje HDM_GETOVERFLOWRECT, pasando en lParam un puntero a una estructura RECT que recibirá la información del rectángulo.

Si el usuario pulsa el botón de overflow se enviará un mensaje de notificación HDN_OVERFLOWCLICK. Tampoco existe un comportamiento predefinido para este mensaje, de modo que de nuevo será la aplicación la encargada de procesarlo.

Mensajes de gestión de mapas de bits

Control de cabecera bitmap

Es posible añadir imágenes a los items del control de cabecera, ya sea en solitario o combinados con texto. Para insertar un mapa de bits hay que indicar en el miembro mask de HDITEM el valor HDF_BITMAP o HDF_BITMAP_ON_RIGHT (si queremos que el mapa de bits aparezca a la derecha del texto), y asignar um mapa de bits al miembro hbm. En este ejemplo cargaremos el mapa de bits desde un recurso:

        hdi.fmt = HDI_FORMAT | HDI_WIDTH | HDF_BITMAP;
        hdi.hbm = LoadBitmap(hInstance, bm);
        ...
        SendMessage(hwndHeader, HDM_INSERTITEM, (WPARAM) iDespuesDe, (LPARAM) &hdi);

Si además queremos añadir un texto, habrá que añadir al miembro mask el valor HDI_TEXT e indicar el formato preferido en el miembro fmt:

        hdi.mask |= HDI_TEXT;
        hdi.fmt = HDI_FORMAT | HDI_WIDTH | HDI_TEXT;
        hdi.fmt = HDI_FORMAT | HDI_WIDTH | HDF_BITMAP_ON_RIGHT | HDF_STRING | HDF_CENTER;
        hdi.pszText = texto; 
        hdi.hbm = LoadBitmap(hInstance, bm);
        ...
        SendMessage(hwndHeader, HDM_INSERTITEM, (WPARAM) iDespuesDe, (LPARAM) &hdi);

Con el mensaje HDM_SETBITMAPMARGIN obtendremos el valor del margen alrededor del mapa de bits, y con HDM_GETBITMAPMARGIN podemos modificar ese valor.

Control de cabecera bitmap

También podemos usar imágenes de una lista de imágenes. Para ello asignaremos una lista de imágenes al control mediante el mensaje HDM_SETIMAGELIST. En wParam se indicará el tipo de lista de imágenes, normal o de estado. En lParam pasaremos el manipulador de la lista de imágenes.

Para recuperar la lista de imágenes actualmente asignada al control, si la hay, se usa el mensaje HDM_GETIMAGELIST en wParam indicaremos que tipo de lista queremos recuperar, normal o de estado. lParam no se usa, y se retorna un manipulador de la lista de imágenes.

Para usar imágenes de una lista primero hay que asignar una lista de imágenes al control. Después, para cada columna, se indicará en mask el valor HDI_IMAGE, en fmt el valor HDF_IMAGE, y en el miembro iImage el índice de la imagen a mostrar:

        // Asignar lista de imágenes:
        SendDlgItemMessage(hwnd, ID_HEADER1, HDM_SETIMAGELIST, (WPARAM)HDSIL_NORMAL, (LPARAM) hIml);
        SendDlgItemMessage(hwnd, ID_HEADER1, HDM_SETIMAGELIST, (WPARAM)HDSIL_STATE, (LPARAM) hImlEstado);
...
        hdi.mask = HDI_FORMAT | HDI_WIDTH | HDI_IMAGE;
        hdi.fmt = HDF_IMAGE;
        hdi.iImage = il; // índice de imágen dentro de la lista
...
        SendMessage(hwndHeader, HDM_INSERTITEM, (WPARAM) iDespuesDe, (LPARAM) &hdi);
Control de cabecera bitmap

La lista de imágenes de estado se usa como sustitución de las cajas de chequeo. La primera imagen de la lista se usará cuando la caja de chequeo no esté marcada, y la segunda para cuando lo esté. Por supuesto, el control debe tener el estilo HDS_CHECKBOXES, y el item la bandera HDF_CHECKBOX. También se debe procesar el mensaje de notificación HDN_ITEMSTATEICONCLICK.

La bandera HDF_BITMAP_ON_RIGHT también se puede aplicar a imágenes procedentes de una lista de imágenes.

Mensajes de codificación de caracteres

Para el soporte de Unicode disponemos de dos mensajes HDM_GETUNICODEFORMAT, sin parámetros. Un valor de retorno cero indica que el control está usando caracteres ANSI, en caso contrario estará usando caracteres Unicode.

Para cambiar el conjunto de caracteres a usar en el control se puede usar el mensaje HDM_SETUNICODEFORMAT, indicando en wParam un valor nulo para usar caracteres ANSI o un valor distinto de cero para usar Unicode.

Acción del ratón sobre items

Se enviará un mensaje de notificación HDN_ITEMCLICK cuando el usuario haga clic con el ratón sobre un item, y un mensaje HDN_ITEMDBLCLICK si hace doble clic.

TAmbién se recibirá un mensaje de notificación NM_RCLICK cuando el usuario haga clic con el botón derecho sobre uno de los items del control. Este mensaje no es específifico del control de cabecera, y se envía para otros controles también, por lo que la aplicación deberá determinar de qué tipo de control proviene el mensaje.

No hay comportamiento por defecto para estos mensajes, de modo que debe ser la aplicación la que responda a ellos de la forma que se considere oportuna.

Notificaciones de modificación de item

Cada vez que se vayan a modificar las propiedades de un item se envían dos mensajes de notificación a la aplicación. El primero es un mensaje HDN_ITEMCHANGING que se envía justo antes de que se modifique el item. Esto da una oportunidad a la aplicación para permitir o no los cambios, o ajustar los nuevos valores a los permitidos por el diseño del programa.

El mensaje de notificación HDN_ITEMCHANGED se envía a la aplicación después de que los cambios se hayan completado. Esto permite que la aplicación pueda procesar el resultado de aplicar esos cambios, por ejemplo, en un ListView actualizaría los datos de la lista.

Pulsaciones de tecla

Se envían mensaje de notificación HDN_ITEMKEYDOWN cuando el usuario pulsa alguna tecla estando activo el control. No se envían notificaciones con todas las teclas.

Inserción con datos incompletos

Al insertar un item en un control de cabecera podemos omitir el texto y el índice de la imagen de la lista de imágenes. En el primer caso usaremos el valor LPSTR_TEXTCALLBACK en lugar del texto para asignar el miemro pszText de la estructura HDITEM. Para el índice de la imagen usaremos el valor I_IMAGECALLBACK para el miembro iImage.

Cuando el control necesite la información ausente la pedirá a la aplicación mediante un mensaje de notificación HDN_GETDISPINFO. La aplicación debe consultar el valor del miembro mask de la estructura HDITEM que recibirá como un puntero en lParam para determinar qué valores está pidiendo el control, y asignará los miembros correspondientes. Si además añade el valor HDI_DI_SETITEM al mask, el control almacenará esos valores y no volverá a solicitarlos.

    NMHDDISPINFO* pdi;
    char* szTextoItem = "Prueba";
... 
        case WM_NOTIFY:
            pnmhdr = (LPNMHEADER)lParam;
            switch(pnmhdr->hdr.code) {
                case HDN_GETDISPINFO:
                    pdi = (NMHDDISPINFO*)lParam;
                    if(pdi->mask & HDI_IMAGE) {
                        pdi->iImage = 4;
                    }
                    if(pdi->mask & HDI_TEXT) {
                        strcpy(pdi->pszText, szTextoItem);
                    }
                    pdi->mask |= HDI_DI_SETITEM;
                    return 0;
              }
... 

Otros mensajes de notificación

Otros mensajes de notificación que se pueden enviar a la aplicación, pero que no son exclusivos de este tipo de controles son NM_CUSTOMDRAW que sirve para personalizar la apariencie del control.

Ejemplo 91

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 91 win091.zip 2021-09-06 5729 bytes 20

Ejemplo 92

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 92 win092.zip 2021-09-06 13466 bytes 28

Ejemplo 93

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 93 win093.zip 2021-09-06 22737 bytes 10