Tutorial 23: Icono de Bandeja [Tray Icon]

En este tutorial, aprenderemos cómo poner iconos en la banadeja de sistema y cómo crear/usar un menú emergente.
Bajate el ejemplo aquí.

Teoría:

La bandeja del sistema es la región rectangular en la barra de tareas donde residen la mayoría de los iconos. Normalmente, verás como mínimo un reloj digital aquí. También puedes poner iconos en la barra de sistema. Debajo están los pasos que tienes que seguir para poner un icono en la barra de sistema:

  1. rellena la estructura NOTIFYICONDATA que tiene los siguientes miembros:
  2. Llama a Shell_NotifyIcon que está definida en shell32.inc. Esta función tiene el siguiente prototipo:

  3.  

                Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

        dwMessage  Es el tipo de mensaje a enviar al shell de Windows.
               NIM_ADD Añade un icono al area de estado.
              NIM_DELETE Borra un icono del area de estado.
              NIM_MODIFY Modifica un icono en el area de estado.
        pnid  es el puntero a la estructura NOTIFYICONDATA rellenada con valores apropiados
    Si quieres añadir un icono a la bandeja, usa el mensage NIM_ADD, si quieres borrar un icono , usa NIM_DELETE.

Eso es todo lo que hay en ella. Pero la mayoría de las veces no estás contento con sólo poner un icono aquí. Necesitas ser capaz de responder a eventos de ratón sobre el icono de bandeja. Puedes hacer esto procesando el mensaje que especificas en el miembro uCallbackMessage de la estructura NOTIFYICONDATA. Este mensaje tiene los siguientes valores en wParam y lParam (agradecimientos especiales a s__d por esta informacion):

La mayoría de los iconos de bandeja, como siempre, muestran un menú emergente cuando el usuario pulsa el botón derecho sobre éste. Podemos implementar esta función creando un menú emergente y entonces llamando a TrackPopupMenu para mostrarlo. Los pasos están descritos abajo:

  1. Crear un menu emergente llamando a CreatePopupMenu. Esta función crea un menu vacío. Esto devuelve un manejador (handle) al menú en eax si el resultado es satisfactorio.
  2. Añade elementos de menú con AppendMenu, InsertMenu o InsertMenuItem.
  3. Cuando quieres mostrar el menú emergente donde está el cursor del ratón, llama a GetCursorPos para obtener las coordenadas de pantalla del cursor y entonces llama a TrackPopupMenu para mostrar el menú. Cuando el usuario selecciona un elemento del menú emergente, Windows manda el mensaje WM_COMMAND a tu procedimiento de ventana como si fuera una selección de menú normal.

Nota: Precaución con dos molestos comportamientos cuando usas menues emergentes con un icoo de bandeja:

  1. Cuando el menú emergente es mostrado, si pulsas fuera del menú, el menú emergente no desaparecerá inmediatamente, como debiera. Este comportamiento ocurre porque la ventana que recibirá la notificación desde el menú emergente DEBERÁ ser la ventana de primer plano. Así que la llamada a SetForegroundWindow corregirá esto.
  2. Despues de llamar a SetForegroundWindow, encontrarás que la primera vez que se muestra el menú emergente, este trabaja bien pero en las siguientes ocaciones, el menú emergente se mostrará y cerrará inmediatamente. Este comportamiento es "intencionado", par citar a MSDN. El cambio de tarea al programa que es dueño del icono de bandeja en un futuro cercano es necesario. Puedes forzar este cambio de tarea enviando cualquier mensage a la ventana del programa. Usa PostMessage, no SendMessage!

Ejemplo:

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib

WM_SHELLNOTIFY equ WM_USER+5
IDI_TRAY equ 0
IDM_RESTORE equ 1000
IDM_EXIT equ 1010
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName  db "TrayIconWinClass",0
AppName    db "TrayIcon Demo",0
RestoreString db "&Restore",0
ExitString   db "E&xit Program",0

.data?
hInstance dd ?
note NOTIFYICONDATA <>
hPopupMenu dd ?

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_APPWORKSPACE
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
           CW_USEDEFAULT,350,200,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    .while TRUE
        invoke GetMessage, ADDR msg,NULL,0,0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .endw
    mov eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL pt:POINT
    .if uMsg==WM_CREATE
        invoke CreatePopupMenu
        mov hPopupMenu,eax
        invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
        invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
    .elseif uMsg==WM_DESTROY
        invoke DestroyMenu,hPopupMenu
        invoke PostQuitMessage,NULL
    .elseif uMsg==WM_SIZE
        .if wParam==SIZE_MINIMIZED
            mov note.cbSize,sizeof NOTIFYICONDATA
            push hWnd
            pop note.hwnd
            mov note.uID,IDI_TRAY
            mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
            mov note.uCallbackMessage,WM_SHELLNOTIFY
            invoke LoadIcon,NULL,IDI_WINLOGO
            mov note.hIcon,eax
            invoke lstrcpy,addr note.szTip,addr AppName
            invoke ShowWindow,hWnd,SW_HIDE
            invoke Shell_NotifyIcon,NIM_ADD,addr note
        .endif
    .elseif uMsg==WM_COMMAND
        .if lParam==0
            invoke Shell_NotifyIcon,NIM_DELETE,addr note
            mov eax,wParam
            .if ax==IDM_RESTORE
                invoke ShowWindow,hWnd,SW_RESTORE
            .else
                invoke DestroyWindow,hWnd
            .endif
        .endif
    .elseif uMsg==WM_SHELLNOTIFY
        .if wParam==IDI_TRAY
            .if lParam==WM_RBUTTONDOWN
                invoke GetCursorPos,addr pt
                invoke SetForegroundWindow,hWnd
                invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
                invoke PostMessage,hWnd,WM_NULL,0,0
            .elseif lParam==WM_LBUTTONDBLCLK
                invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
            .endif
        .endif
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp

end start
 

Análisis:

El programa mostrará una simple ventana. Cuando aprietes el botón minimizar, se ocultará y pondrá un icono en la bandeja del sistema. Cuando hagas una doble pulsación sobre el icono, el programa se restablecerá y borrara el icono de la bandeja de sistema. Cuando pulses el botón derecho sobre este, se mostrará un menú emergente. Puedes elegir si restaurar el programa o salir de él.

   .if uMsg==WM_CREATE
        invoke CreatePopupMenu
        mov hPopupMenu,eax
        invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
        invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString

Cuando se crea la ventana principal, crea un menu emergente y le añade dos elementos. AppendMenu tiene la siguiente sintaxis:
 

AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
 

Después de que es creado el menú emergente, la ventana principal espera pacientemente a que el usuario pulse el botón de minimizar.
Cuando la ventana es minimizada, recibe el mensage WM_SIZE con el valor SIZE_MINIMIZED en wParam.

    .elseif uMsg==WM_SIZE
        .if wParam==SIZE_MINIMIZED
            mov note.cbSize,sizeof NOTIFYICONDATA
            push hWnd
            pop note.hwnd
            mov note.uID,IDI_TRAY
            mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
            mov note.uCallbackMessage,WM_SHELLNOTIFY
            invoke LoadIcon,NULL,IDI_WINLOGO
            mov note.hIcon,eax
            invoke lstrcpy,addr note.szTip,addr AppName
            invoke ShowWindow,hWnd,SW_HIDE
            invoke Shell_NotifyIcon,NIM_ADD,addr note
        .endif

Aprovechamos esta oportunidad para rellenar la estructura NOTIFYICONDATA. IDI_TRAY es una constante definida al principio del código fuente. Puedes ponerlo al valor que quieras. Esto no importa porque tienes un único icoo de bandeja. Pero si vas a poner varios iconos en la bandeja del sistema necesitarás un ID único para cada icono de bandeja. Especificamos todas las banderas en el miembro uFlags porque especificamos un icono (NIF_ICON), especificamos un mensaje común (NIF_MESSAGE) y especificamos el texto tooltip (NIF_TIP). WM_SHELLNOTIFY es un mensaje común definido como WM_USER+5. El valor actual no es importante siempre que sea único. Yo uso aquí el icono winlogo como icono de bandeja pero puedes usar cualquier icono en tu programa. Cárgalo desde el recurso con LoadIcon y pon el manejador (handle) devuelto en el miembro hIcon. Finalmente, rellenamos el szTip con el texto que queremos que muestre el shell cuando el ratón está sobre el icono.

Ocultamos la ventana padre para dar la ilusión de parecer minimizar al icono de bandeja.

Después podemos llamar a Shell_NotifyIcon con el mensage NIM_ADD para añadir el icono a la barra del sistema.

Ahora nuestra ventana principal está oculta y el icono está en la banedja del sistema. Si mueves el ratón sobre él verás el tooltip que muestra el texto que ponemos dentro del miembro szTip. Después, si haces una pulsación doble en el icono, la ventana principal reaparecerá y el icono de bandeja se irá.

    .elseif uMsg==WM_SHELLNOTIFY
        .if wParam==IDI_TRAY
            .if lParam==WM_RBUTTONDOWN
                invoke GetCursorPos,addr pt
                invoke SetForegroundWindow,hWnd
                invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
                invoke PostMessage,hWnd,WM_NULL,0,0
            .elseif lParam==WM_LBUTTONDBLCLK
                invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
            .endif
        .endif

Cuando ocurre algún evento de ratón sobre el icono de bandeja, tu ventana recibe el mensaje WM_SHELLNOTIFY que es el mensage común que especificas en el miembro uCallbackMessage. Vuelve a llamar a esta recibiendo el mensaje, wParam contiene la ID del icono de bandeja y lParam contiene el mensaje de ratón actual. En el código de arriba, primero chequeamos si este mensaje viene del icono de bandeja que nos interesa. Si esto ocurre, chequeamos el mensaje del ratón. Como estamos interesados únicamente en la pulsación derecha o en la doble pulsación izquierda, procesamos sólo los mensages WM_RBUTTONDOWN y WM_LBUTTONDBLCLK.

Si el mensaje de ratón es WM_RBUTTONDOWN llamamos a GetCursorPos para obtener las coordenadas actuales en la pantalla del ratón. Cuando la función vuelve, la estructura POINT es rellenada con las coordenadas en pantalla del ratón. Port coordenada de pantalla, quiero decir la coordenada de toda la pantalla sin considerar a ninguno de los bordes de ninguna ventana. Por ejemplo, si la resolución de la pantalla es 640*480, la esquina inferior derecha de la pantalla es x==639 e y==479. Si quieres convertir la coordenada de pantalla a coordenada de ventana usa la función ScreenToClient.

Como siempre, para nuestro propósitos, queremos mostrar el menú emergente en la posición actual del ratón con la llamada a TrackPopupMenu y esto requiere coordenadas de pantalla, podemos usar las coordenadas rellenadas directamente en GetCursorPos.
TrackPopupMenu tiene la siguiente sintaxis:
 

Cuando el usuario hace una doble pulsación sobre el icono de la bandeja mandamos el mensaje WM_COMMAND a nuestra ventana especificando IDM_RESTORE para emular la selección del usuario del elemento Restore [Restaurar] del menú emergente, restaurando la ventana principal y borrando el icono de la bandeja del sistema. Para poder estar preparados para recibir el mensaje de doble pulsación, la ventana principal deberá tener el estilo CS_DBLCLKS.

            invoke Shell_NotifyIcon,NIM_DELETE,addr note
            mov eax,wParam
            .if ax==IDM_RESTORE
                invoke ShowWindow,hWnd,SW_RESTORE
            .else
                invoke DestroyWindow,hWnd
            .endif

Cuando el usuario selecciona el elemento del menú Restore, borramos el icono de la bandeja llamando otra vez a Shell_NotifyIcon, ahora especificamos como mensaje NIM_DELETE. Lo siguiente es restaurar la ventana principal a su estado original. Si el usuario selecciona Exit en el menu, también borramos el icono de la barra y destruimos la ventana principal llamando a DestroyWindow.


Index

Next

[Iczelion's Win32 Assembly HomePage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por  JoTaKe, y revisado por:   n u M I T_o r

www.000webhost.com