Notificaciones

Los controles tooltip disponen de algunos mensajes de notificación que nos permiten controlar de forma detallada algunos aspectos, veremos ahora algunos de ellos.

Estos mensajes de notificación vienen el el formato de un mensaje WM_NOTIFY, donde wParam contiene el identificador del control que envía el mensaje, y lParam es un puntero a una estructura NMHDR, o una estructura cuyo primer campo es una estructura NMHDR y otros campos adicionales, que dependen del control que envía el mensaje. Este es el caso de los mensajes de notificación de los tooltips.

Generalmente, el valor de wParam no resulta útil, ya que varios controles pueden tener el mismo identificador. En su lugar se usará alguno de los campos de NMHDR, o de la estructura específica usada por el control.

Mensaje de petición de texto

Nota: Hay dos mensajes de notificación equivalentes, TTN_GETDISPINFO y TTN_NEEDTEXT. Los dos tienen el mismo valor, pero el primero sustituye al segundo, que probablemente quedará obsoleto en el futuro. Cada mensaje tiene una estructura de datos asociada, el primero NMTTDISPINFO y el segundo TOOLTIPTEXT, de nuevo, el segundo ha sido sustituido, y no lo usaremos.

El funcionamiento de este mensaje es un poco complicado de explicar, veremos si puedo hacerlo claramente.

Existe una tercera forma de asignar textos a un tooltip. Si cuando añadimos un control a un tooltip especificamos la constante LPSTR_TEXTCALLBACK para el campo lpszText, estaremos indicando que el texto del tooltip para ese control se obtendrá de la propia aplicación cada vez que sea necesario.

    TOOLINFO toolInfo = { 0 };

        toolInfo.cbSize = sizeof(toolInfo);
        toolInfo.hwnd = hwnd;
        toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
        toolInfo.uId = (UINT_PTR)GetDlgItem(hwnd, CM_DESHABILITAR);
        toolInfo.lpszText = LPSTR_TEXTCALLBACK; // <- Asignar texto mediante notificación
        SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);

Cuando se vaya a mostrar un tooltip para un control para el que se ha especificado la constante LPSTR_TEXTCALLBACK para el campo lpszText, se envía un mensaje de notificación TTN_GETDISPINFO, a travé de un mensaje WM_NOTIFY a la ventana padre del tooltip, que deberemos procesar. Cuando recibamos un mensaje de notificación WM_NOTIFY, generalmente podremos ignorar el parámetro wParam, y en un primer lugar, tomaremos el parámetro lParam como un puntero a una estructura NMHDR, ya que no sabemos qué tipo de control ha enviado la notificación, y por lo tanto, no sabemos que estructura viene apuntada por ese parámetro.

    LPNMHDR pnmhdr;
...
        case WM_NOTIFY:
           pnmhdr = (LPNMHDR)lParam;
           switch(pnmhdr->code) {
...

Una vez que sabemos qué tipo de mensaje de notificación hemos recibido, también podremos determinar qué tipo de estructura viene apuntada por lParam, y, en el caso concreto del mensaje de notificación TTN_GETDISPINFO, sabremos que se trata de una estructura TOOLTIPTEXT. Por lo tanto, trataremos a ese parámetro como un puntero a una de esas estructuras.

    LPNMHDR pnmhdr;
    LPNMTTDISPINFO pnmttdispinfo;
...
        case WM_NOTIFY:
           pnmhdr = (LPNMHDR)lParam;
           switch(pnmhdr->code) {
             case TTN_GETDISPINFO:
                pnmttdispinfo = (LPNMTTDISPINFO)pnmhdr;

A continuación tendremos que identificar el control concreto para el que se ha generado la notificación del tooltip. Para ello tendremos que consultar el miembro uFlags de la estructura NMTTDISPINFO, para ver si contiene la bandera TTF_IDISHWND. En ese caso, el campo idFrom de la estructura NMHDR que es el primer campo de NMTTDISPINFO contendrá un manipulador de la ventana. Esto será lo normal, ya que nosotros habremos especificado esa bandera al añadir el control al tooltip, pero no está de más verificarlo.

Como ese campo es un manipulador de ventana, podremos obtener el identificador del control mediante una llamada a la función GetDlgCtrlID:

    LPNMHDR pnmhdr;
    LPNMTTDISPINFO pnmttdispinfo;
...
        case WM_NOTIFY:
           pnmhdr = (LPNMHDR)lParam;
           switch(pnmhdr->code) {
             case TTN_GETDISPINFO:
                pnmttdispinfo = (LPNMTTDISPINFO)pnmhdr;
                if (pnmttdispinfo->uFlags & TTF_IDISHWND) {
                    switch(GetDlgCtrlID((HWND)pnmttdispinfo->hdr.idFrom)) {
                       case CM_DESHABILITAR:  
...

Ahora tenemos varias formas de indicar el texto para el tooltip, elegiremos la que más nos convenga:

  • Copiar una cadena de 80 caracteres o menos (incluido en nulo terminador), en el campo szText de la estructura NMTTDISPINFO.
    strcpy(pnmttdispinfo->szText, "Deshabilita los tooltips.");
    
  • Asignar la dirección de una cadena al campo lpszText de la estructrua NMTTDISPINFO.
    pnmttdispinfo->lpszText = "Deshabilita los tooltips.";
    
  • Asignar un identificador de recurso de cadena campo lpszText y un manipulador de instancia al miembro hinst de la estructrua NMTTDISPINFO.
    pnmttdispinfo->lpszText = STR_DESHABILITAR;
    pnmttdispinfo->hinst = hInstance;
    

Si la cadena no va a modificarse a lo largo de la vida de la aplicación, podemos añadir la bandera TTF_DI_SETITEM al campo uFlags de la estructura NMTTDISPINFO. Esto hará que el tooltip almacene el texto y no vuelva a generar una notificación para el mismo control. Si por el contrario, el texto es variable, podemos omitir esa bandera y volveremos a recibir notificaciones para el mismo control cada vez que sea necesario visualizar su tooltip.

El ejemplo completo queda así:

    LPNMHDR pnmhdr;
    LPNMTTDISPINFO pnmttdispinfo;
...
        case WM_NOTIFY:
           pnmhdr = (LPNMHDR)lParam;
           switch(pnmhdr->code) {
             case TTN_GETDISPINFO:
                pnmttdispinfo = (LPNMTTDISPINFO)pnmhdr;
                if (pnmttdispinfo->uFlags & TTF_IDISHWND) {
                    switch(GetDlgCtrlID((HWND)pnmttdispinfo->hdr.idFrom)) {
                       case CM_DESHABILITAR:
                          pnmttdispinfo->uFlags |= TTF_DI_SETITEM;
                          strcpy(pnmttdispinfo->szText, "Deshabilita los tooltips.");
                          break;
                       case CM_HABILITAR:
                          pnmttdispinfo->uFlags |= TTF_DI_SETITEM;
                          pnmttdispinfo->lpszText = "Habilita los tooltips.";
                          break;
                    }
                }
                break;
           }
           break;

Ejemplo 88


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 88 win088.zip 2012-06-15 3613 bytes 79

Notificaciones de mostrar y ocultar

Cada vez que una ventana tooltip vaya a ser mostrada se envía un mensaje de notificación TTN_SHOW y cuando va a ser ocultada, uno TTN_POP. Ambos se envían mediante un mensaje WM_NOTIFY, y en ambos casos, el parámetro lParam contiene un puntero a una estructura NMHDR.

No he encontrado ninguna utilidad a estos mensajes. El único ejemplo que se me ha ocurrido es contar cada vez que se muestra u oculta el tooltip, y mostrar esa cuenta en la ventana. También se podría usar esta notificación para visualizar un texto en la barra de estado.

        case WM_NOTIFY:
           pnmhdr = (LPNMHDR)lParam;
           switch(pnmhdr->code) {
             case TTN_SHOW:
                mostrado++;
                InvalidateRect(hwnd, NULL, TRUE);
                break;
             case TTN_POP:
                ocultado++;
                InvalidateRect(hwnd, NULL, TRUE);
                break;
			}
			break;
...	
        case WM_PAINT:
           hdc = BeginPaint(hwnd, &ps);
           SetBkMode(hdc, TRANSPARENT);
           sprintf(cad, "Mostrado=%d  Ocultado=%d", mostrado, ocultado);
           TextOut(hdc, 10, 40, cad, strlen(cad));
           EndPaint(hwnd, &ps);
           break;

Personalización

Es posible personalizar un tooltip, cambiando la fuente, los colores o encargando a la aplicación el dibujo de algunos detalles del tooltip.

El mensaje de notificación NM_CUSTOMDRAW se envía para muchos controles, incluídos algunos que ya hemos visto, como por ejemplo, los botones.

En el caso de los tooltips, los cambios de fuente que hagamos sólo tendrán enfecto en el estilo clásico. Si activamos los estilos visuales, los cambios de fuente serán ignorados.

En el caso que nos ocupa recibiremos un mensaje de notificación NM_CUSTOMDRAW justo antes de que se empiece a pintar el control, con el código de etapa de dibujo CDDS_PREPAINT. Esto nos permite tomar varias acciones:

  • Modificar la fuente, en cuyo caso debemos retornar el valor CDRF_NEWFONT. Esto indica al procedimiento de ventana del control que debe recalcular el tamaño del texto, y por lo tanto, el del control.
  • Modificar el color del texto, mediante la función SetTextColor. En ese caso podemos retornar CDRF_DODEFAULT, para que el procedimiento siga su curso normnal.

  • Retornar el valor CDRF_NOTIFYPOSTPAINT, para que volvamos a recibir un mensaje de notificación cuando el tooltip haya sido dibujado del todo. En este caso, el código de etapa será CDDS_POSTPAINT.

El resto de los códigos de retorno no se aplican a los tooltips, o al menos no parece que funcionen. En muchos casos porque los tooltips no tienen ítems ni subítems.

Si hemos retornado CDRF_NOTIFYPOSTPAINT recibiremos un mensaje de notificación con el código CDDS_POSTPAINT. Podemos aprovechar esta notificación para hacer retoques en el tooltip, y digo retoques, porque el tooltip ya estará pintado. Por supuesto, esos "retoques" pueden incluir borrar el contenido completo del tooltip y volver a pintarlo.

En este ejemplo procesamos los mensaje WM_NOTIFY para que el tooltip del control identificado con CM_BOTON3 se muestre con otra fuente, en color rojo y con un borde grueso.

    LPNMHDR pnmhdr;
    LPNMTTCUSTOMDRAW pnmttcustomdraw;
...
        case WM_CREATE:
...
           hFont = CreateFont(-30, 0, 0, 450, 300, FALSE, FALSE, FALSE,
                DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
                PROOF_QUALITY, DEFAULT_PITCH | FF_ROMAN, "Times New Roman");
           hPen = CreatePen(PS_SOLID, 4, RGB(255,0,0));
...
        case WM_NOTIFY:
           pnmhdr = (LPNMHDR)lParam;
           switch(pnmhdr->code) {
             case TTN_SHOW:
                mostrado++;
                InvalidateRect(hwnd, NULL, TRUE);
                break;
             case TTN_POP:
                ocultado++;
                InvalidateRect(hwnd, NULL, TRUE);
                break;
             case NM_CUSTOMDRAW:
                pnmttcustomdraw = (LPNMTTCUSTOMDRAW)pnmhdr;
                if(pnmttcustomdraw->nmcd.hdr.hwndFrom == hwndTip &&
                   GetDlgCtrlID((HWND)pnmttcustomdraw->nmcd.hdr.idFrom) == CM_BOTON3 &&
                   pnmttcustomdraw->nmcd.dwDrawStage == CDDS_PREPAINT) {
                    SelectObject(pnmttcustomdraw->nmcd.hdc, hFont);
                    SetTextColor(pnmttcustomdraw->nmcd.hdc, RGB(255,0,0));
                    return CDRF_NEWFONT | CDRF_NOTIFYPOSTPAINT;
                } else
                if(pnmttcustomdraw->nmcd.hdr.hwndFrom == hwndTip &&
                   GetDlgCtrlID((HWND)pnmttcustomdraw->nmcd.hdr.idFrom) == CM_BOTON3 &&
                   pnmttcustomdraw->nmcd.dwDrawStage == CDDS_POSTPAINT) {
                    SelectObject(pnmttcustomdraw->nmcd.hdc, GetStockObject(HOLLOW_BRUSH));
                    SelectObject(pnmttcustomdraw->nmcd.hdc, hPen);
                    Rectangle(pnmttcustomdraw->nmcd.hdc,
                              pnmttcustomdraw->nmcd.rc.left,
                              pnmttcustomdraw->nmcd.rc.top,
                              pnmttcustomdraw->nmcd.rc.right,
                              pnmttcustomdraw->nmcd.rc.bottom);
                    return CDRF_SKIPDEFAULT;
                } else
                return CDRF_DODEFAULT;
                break;
           }
           break;
...
        case WM_DESTROY:
           DeleteObject(hFont);
           DeleteObject(hPen);
...

Ejemplo 89


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 89 win089.zip 2012-06-15 4478 bytes 129

Otros mensajes

Los tooltips disponen de muchos otros mensajes, algunos de los cuales los explicaremos cuando veamos otros controles comunes, como tree-views y list-views, y otros que problemente no veamos, porque tienen poca utilidad.

Esta es una lista del resto de los mensajes que no hemos visto en este capítulo: