Tutorial 23: Icône de Barre de Taches
(Tray Icon)

Dans ce Tutorial, nous allons voir comment mettre des icônes dans le 'System Tray'(système de barre de tâches) et comment créer/employer un menu popup.

Downloadez l'exemple ici. Vous obtiendrez une fenêtre, qui si vous la réduisez au maximum placera son icône dans la barre de tâches au lieu de la laisser à côté du menu 'Démarrer'.

Théorie:

Le 'System Tray' est la région rectangulaire dans la barre de tâche où plusieurs icônes sont rangées. Normalement, vous y verrez au moins l'icône de l'horloge digitale. Vous pouvez aussi mettre des icônes sur le 'System Tray'. Ci-dessous voici les étapes que vous devez exécuter pour mettre une icône sur le 'System Tray' :
  1. Remplissez la structure NOTIFYICONDATA avec les membres suivants:
  2. Call Shell_NotifyIcon laquelle est définie dans shell32.inc. Cette fonction a le prototype suivant :

  3.             Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

        dwMessage  est le type de message à envoyer au Shell.
               NIM_ADD ajoute une icône au secteur de statut.
              NIM_DELETE supprime une icône du secteur de statut.
              NIM_MODIFY modifie une icône dans le secteur de statut.
        pnid  est le pointer qui est sur la structure NOTIFYICONDATA. Elle doit être remplie des valeurs appropriées.
    Si vous voulez ajouter une icône dans le 'System Tray', vous devez utiliser le message NIM_ADD, si vous voulez enlever l'icône, il faut employer NIM_DELETE.

C'est tous pour le moment. Mais la plupart du temps, on ne se contente pas seulement de mettre une icône à cet endroit. On a besoin d'être capables de répondre aux événements (au clique) de la souris sur un 'tray icon'. On peut faire ça en traitant le message que nous avons spécifié dans le membre uCallbackMessage de la structure NOTIFYICONDATA. Ce message prend les valeurs suivantes dans wParam et lParam (un petit remerciement spécial à s__d pour ses renseignements) : Cependant, beaucoup de 'Tray Icons' déploient un menu popup quand l'utilisateur clique dessus. Nous pouvons exécuter cette particularité en créant un menu popup et en appelant ensuite TrackPopupMenu pour l'afficher. Les étapes sont décrits ci-dessous :
  1. Créez un menu popup en appelant CreatePopupMenu. Cette fonction crée un menu vide. Elle renvoie l'handle du menu dans eax, en cas de succès.
  2. Ajoutez-y des Items de menu grâce à AppendMenu, InsertMenu or InsertMenuItem.
  3. Lorsque vous souhaitez montrer le menu popup à l'endroit où est placée le curseur de la souris, appelez GetCursorPos pour obtenir la coordonnée du curseur sur l'écran puis appeler ensuite TrackPopupMenu pour afficher le menu. Quand l'utilisateur choisit un Item (un article) du menu popup (c'est aussi un sous menu si vous préférez), Windows envoie le message WM_COMMAND à votre procédure de fenêtre exactement comme pour une sélection d'un menu normal.
Note : Prenez garde de deux choses un peu ennuyeuses lorsque vous utilisez un menu popup avec un 'Tray Icon' :
  1. Si vous cliquez n'importe où à l'extérieur du menu popup lorsqu'il est affiché, celui-ci ne disparaîtra pas immédiatement comme il devrait le faire. Ça se passe comme ça parce que la fenêtre qui recevra les avis du menu popup DOIT ÊTRE la fenêtre de premier plan. Le simple appel SetForegroundWindow corrigera ce défaut.
  2. Après l'appel SetForegroundWindow, vous constaterez que la toute première fois où le menu popup est affiché, ça fonctionne bien mais les fois suivantes, notre menu popup s'ouvrira puis se fermera immédiatement. Ce comportement n'est "pas intentionnel", pour citer MSDN. Le commutateur de tâche du programme, lequel contrôle le 'tray icon', est nécessaire. Vous pouvez forcer ce commutateur de tâche en postant n'importe quel message à la fenêtre du programme. Utilisez juste le simple message 'PostMessage', et non pas 'SendMessage'!

Exemple:

.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
 

Analyse:

Notre programme affichera une simple fenêtre. Lorsque vous appuyez sur le bouton de réduction (au minimum), il se cachera et mettra une icône dans le 'system tray' tout en bas à droite à côté de votre horloge. Quand vous double cliquez sur l'icône, le programme se rétablira et enlèvera l'icône du 'system tray'. Quand vous simple-cliquez dessus, un menu popup est affiché. Grâce à ce menu, vous pourrez soit le rétablir (réagrandir) soit en sortir.

   .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

Quand la fenêtre principale est créée, on crée un menu popup et on y ajouter deux Items (deux sous-menu). La fonction 'AppendMenu' a la syntaxe suivante :
 

AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
 
Après que le menu popup est été créé, la fenêtre principale attend patiemment que l'utilisateur appuie sur le bouton de réduction (minimum).
Quand une fenêtre est réduite au minimum, elle reçoit le message WM_SIZE avec la valeur SIZE_MINIMIZED dans 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

Nous profitons de cette occasion pour remplir la structure NOTIFYICONDATA. 'IDI_tray' est juste une constante définie au début du code source. Vous pouvez lui donner la valeur que vous souhaitez. Ce n'est pas important puisque on a seulement un seul 'Tray Icon'. Mais si on en avait plusieurs dans notre 'System Tray', on aurait besoin d'IDs uniques pour chaque 'tray icon'. On définit tous les flags suivants dans le membre uFlags parce que nous spécifions une icône (NIF_ICON), nous spécifions un (message sur mesure NIF_MESSAGE et nous spécifions le texte tooltip (NIF_TIP) (Le texte qui est pésenté dans une bulle). WM_SHELLNOTIFY est seulement un message sur mesure (custum) défini comme WM_USER+5. La valeur réelle n'est pas importante tant qu'elle reste unique. Ici, j'emploie l'icône winlogo comme 'tray icon' mais vous pouvez employer n'importe quelle icône dans votre programme. Chargez-le juste en ressource avec LoadIcon et mettez l'handle renvoyé dans le membre hIcon. Finalement, nous mettons (dans szTip) le texte que nous voulons que la bulle affiche lorsque la souris passe au dessus de notre icône.
Nous cachons la fenêtre principale pour donner l'illusion de sa disparition en la "réduisant au minimum en une icône du 'System Tray'".
Ensuite nous appelons Shell_NotifyIcon avec le message NIM_ADD pour ajouter une icône au system tray.

Maintenant notre fenêtre principale est cachée et l'icône est réduite dans le 'System Tray'. Si vous passez la souris au-dessus, vous verrez un tooltip (une bulle) qui montre le texte que nous avons mis dans le membre szTip. Ensuite, si vous double cliquez sur l'icône, la fenêtre principale réapparaîtra et le 'tray icon' disparaîtra.

    .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

Quand un événement souris arrive au tray icon (un clique de souris sur notre icône), notre fenêtre reçoit le message WM_SHELLNOTIFY qui est le message fait sur mesure que nous avons déclaré dans le membre uCallbackMessage. Rappelons-nous qu'à la réception de ce message, wParam contient l'ID du 'tray icon' et lParam contient le message actuel de la souris. Dans le code ci-dessus, nous vérifions d'abord si ce message vient du 'tray icon' auquel nous nous intéressons. Si c'est le cas, on vérifie le message actuel de la souris. Puisque nous sommes uniquement intéressés au 'clique droit de la souris' et au "double clique gauche", on s'occupe seulement des messages WM_RBUTTONDOWN et WM_LBUTTONDBLCLK.
Si le message de la souris est WM_RBUTTONDOWN, nous appelons GetCursorPos pour obtenir les coordonnées actuelles du curseur de la souris sur l'écran. Après avoir reçu les données renvoyées par cette fonction, la structure POINT est remplie des coordonnées du curseur de la souris. Par les coordonnées, je veux dire les coordonnées entières sans qu'il n'y ait aucune frontière de fenêtre (on parle vraiment de l'écran dans sa totalité). Par exemple, si la résolution de votre écran est 640*480, le coin 'inférieur droit' de l'écran est x = 639 et y = 479. Si vous voulez convertir les coordonnées de l'écran en coordonnées de fenêtre, utilisez la fonction ScreenToClient.
Cependant, pour notre but, nous voulons afficher le menu popup à la position actuelle du curseur de la souris avec l'appel TrackPopupMenu et cette fonction exige des coordonnées d'écran, nous pouvons donc directement utiliser les coordonnées remplies par GetCursorPos.
TrackPopupMenu a la syntaxe suivante :
 

Quand l'utilisateur double clique sur le 'tray icon', on envoie un message WM_COMMAND à notre propre fenêtre en spécifiant IDM_RESTORE pour imiter une sélection faite par l'utilisateur pour 'restorer'(réagrandir) la fenêtre principale et enlever l'icône (de notre programme) du 'System Tray'. Pour être capable de recevoir le message de double-clic, la fenêtre principale doit avoir le style 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

Quand l'utilisateur sélectionne l'Item de 'restoration' du menu, nous enlevons le 'tray icon' en appelant à nouveau Shell_NotifyIcon, cette fois nous spécifions NIM_DELETE comme message. Ensuite, nous rétablissons la fenêtre principale à son état d'origine. Si l'utilisateur choisit l'Item 'de Sortie' du menu, nous enlevons aussi l'icône du 'System tray' et détruisons la fenêtre principale en appelant DestroyWindow.


[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte