Colocar un icono en el área de notificación

Icono

A veces puede ser interesante que nuestras aplicaciones coloquen un icono en el área de notificación.

Sin embargo, tampoco conviene abusar de esta técnica. El área de notificación se diseñó para proporcionar mensajes importantes al usuario, pero si se añaden demasiados iconos esta funcionalidad puede perder gran parte de su eficacia.

Las aplicaciones candidatas para colocar iconos en la zona de notificación son aquellas que no necesitan mantener un interfaz con el usuario, y que se ejecutan en segundo plano. El icono indica al usuario que el programa se está ejecutando. Cambiando el icono, la aplicación puede enviar mensajes cuando se requiera la atención del usuario, ya sea mediante un texto, o modificando la apariencia del icono. Además, sirve como punto para que el usuario acceda a un menú contextual, o para que maximice la ventana asociada.

La información que se encuentra por Internet sobre este tema es abundante, pero casi nadie explica cómo hacerlo usando sólo el API de Windows. Siempre he pensado que sería difícil de hacer, pero la verdad es que es muy fácil, así que paso a explicarlo.

Funciones, estructuras y mensajes del API

Lo he puesto en plural, pero lo cierto es que sólo nos interesa una estructura de datos, una función del API y tres mensajes.

La estructura es NOTIFYICONDATA, que actualmente tiene esta definición:

typedef struct _NOTIFYICONDATA {
  DWORD cbSize;
  HWND  hWnd;
  UINT  uID;
  UINT  uFlags;
  UINT  uCallbackMessage;
  HICON hIcon;
  TCHAR szTip[64];
  DWORD dwState;
  DWORD dwStateMask;
  TCHAR szInfo[256];
  union {
    UINT uTimeout;
    UINT uVersion;
  };
  TCHAR szInfoTitle[64];
  DWORD dwInfoFlags;
  GUID  guidItem;
  HICON hBalloonIcon;
} NOTIFYICONDATA, *PNOTIFYICONDATA;

En versiones anteriores a Windows 2000, los miembros desde dwState en adelante no existían, y se han ido añadiendo en sucesivas versiones del API. Para un uso básico no necesitaremos esos datos miembro.

Y la función es Shell_NotifyIcon, que tiene este prototipo:

WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
    DWORD dwMessage,    // message identifier
    PNOTIFYICONDATA pnid    // pointer to structure
);

Hay, además, tres mensajes:

NIM_ADD
NIM_DELETE
NIM_MODIFY

Y en las últimas versiones del API se han añadido dos mensajes más:

NIM_SETFOCUS
NIM_SETVERSION

De todos modos, no hablaré de ellos aquí, porque no los vamos a necesitar.

En una entrada futura hablaremos de otras posibilidades del área de notificación, como mensajes emergentes en forma de globo.

Añadir un icono

Usaremos la función Shell_NotifyIcon para enviar un mensaje NIM_ADD, que insertará un icono en el área de notificación.

Previamente rellenaremos una estructura NOTIFYICONDATA con los valores adecuados.

#define IDENTIFICADOR 3200
#define MENSAJE WM_USER+11
void MostrarIcono(HWND hwnd) {
    HICON hIcon = LoadIcon(GetModuleHandle(0), "Icono");
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(nid);
    nid.hWnd = hwnd;
    nid.uID = IDENTIFICADOR;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.uCallbackMessage = MENSAJE;
    nid.hIcon = hIcon;
    strcpy(nid.szTip, "Estoy en el área de notificación!");
    Shell_NotifyIcon(NIM_ADD, &nid);
    DestroyIcon(hIcon);
}

El miembro cbSize debe contener el tamaño de la estructura. En general, las estructuras que tienen un miembro con este nombre es porque son diferentes en cada versión de Windows, o al menos está previsto que su estructura cambie. Este es el caso, ya que esta estructura ha crecido desde Windows 2000, XP y Vista.

El miembro hWnd debe contener un manipulador de la ventana de la aplicación asociada al icono.

El miembro uID contiene un identificador de la pequeña ventana dentro del área de notificación, asociada al icono. Se usa para enviar mensajes procedentes del icono relacionados con el ratón.

uFlags contiene una combinación de banderas que indica qué campos de la estructura contienen valores válidos. En este caso, NIF_ICON indica que hIcon contiene un manipulador de icono, NIF_MENSAJE indica que uCallbackMessage contiene un código de mensaje válido, y NIF_TIP que szTip contiene una cadena para un tooltip asociado al icono. Este texto se mostrará cuando el ratón se sitúe en el icono.

En uCallbackMessage almacenaremos un valor entero que será usado como identificador de mensaje. Este mensaje se enviará a la ventana asociada, que hemos indicado en el campo hWnd. En wParam se enviará el valor de uID y en lParam el mensaje detectado, por ejemplo WM_MOUSEMOVE o WM_LBUTTONDOWN.

Por si no lo has notado, la misma aplicación puede poner varios iconos diferentes en el área de notificación. Los mensajes procedentes de cada uno de ellos se distinguen por el valor de uID.

En hIcon colocaremos el manipulador del icono que queremos mostrar. Es importante destruir el manipulador después de llamar a la función Shell_NotifyIcon, ya que de no hacerlo se pueden producir fugas de memoria.

Por último, en szTip copiaremos la cadena a usar como texto en el tooltip asociado al icono. Esta cadena está limitada a 64 caracteres.

Modificar el icono

Podemos cambiar el icono cuando queramos.

Por ejemplo, supongamos que el icono que se muestra cuando la aplicación se está ejecutando correctamente, y no necesita atención por parte del usuario es Icono1. En un momento determinado se produce una situación que queremos notificar. En ese caso, procedemos a cambiar el icono por Icono2. Podemos modificar también el texto del tooltip, para dar una pista sobre la nueva situación.

void CambiarIcono(HWND hwnd, HICON hIcon) {
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(nid);
    nid.hWnd = hwnd;
    nid.uID = IDENTIFICADOR;
    nid.uFlags = NIF_ICON;
    nid.hIcon = hIcon;
    Shell_NotifyIcon(NIM_MODIFY, &nid);
    DestroyIcon(hIcon);
}

En este caso usaremos el mensaje NIM_MODIFY, y como sólo queremos cambiar el icono, uFlags contendrá el valor NIF_ICON.

Por supuesto, en hIcon colocaremos el manipulador del nuevo icono a mostrar.

Modificar el texto

De un modo similar, podemos modificar el texto del tooltip.

void CambiarTexto(HWND hwnd, char *cad) {
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(nid);
    nid.hWnd = hwnd;
    nid.uID = IDENTIFICADOR;
    nid.uFlags = NIF_TIP;
    strcpy(nid.szTip, cad);
    Shell_NotifyIcon(NIM_MODIFY, &nid);
}

Ahora uFlags debe valer NIF_TIP, y deberemos modificar el valor de szTip.

Quitar icono de área de notificación

Para eliminar un icono usaremos el mensaje NIM_DELETE

void OcultarIcono(HWND hwnd) {
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = hwnd;
    nid.uID = IDENTIFICADOR;

    Shell_NotifyIcon(NIM_DELETE, &nid);
}

No necesitamos especificar uFlags, ni ninguno de los valores opcionales.

Responder a mensajes desde el área de notificación

Si hemos indicado el valor NIF_MESSAGE al añadir el icono, el procedimiento de ventana asociado recibirá un mensaje MENSAJE. En wParam recibiremos el identificador asociado al icono, y en lParam el mensaje original.

Para procesar estos mensajes sólo hay que añadir el caso adecuado para MENSAJE.

switch (message) { /* handle the messages */
...
    case MENSAJE:
        if(wParam == IDENTIFICADOR) {
            switch(lParam) {
                 case WM_LBUTTONUP:
                     OcultarIcono(hwnd);
                     ShowWindow (hwnd, SW_RESTORE);
                     break;
            }
        }
        break;
...

Este ejemplo restaurará la ventana asociada al icono cuando se detecte el que el botón izquierdo del ratón fue soltado sobre el icono del área de notificación. Por supuesto, podemos procesar todos los mensajes que se reciban desde el icono, como WM_MOUSEMOVE, WM_LBUTTONDOWN, etc.

Ejemplo completo

Nombre Fichero Fecha Tamaño Contador Descarga
Iconos en área de notificación icononotificacion.zip 2020-09-27 58992 bytes 500