Iczelion - 23 - Tray Icon

Tutorial 23: Tray Icon


In diesem Tutorial werden wir lernen wie man Icons in der System Tray plaziert und wie man Popup-Menüs erzeugt/benutzt.
Laden Sie das Beispiel hier herunter.

Theorie:

Der System Tray ist der rechteckige Bereich in der Taskbar, wo sich mehrere Icons befinden. Normalerweise sehen Sie dort mindestens die digital Uhr drin. Sie können auch Icons im System Tray plazieren. Dazu müssen Sie folgende Schritte ausführen:
  1. Füllen Sie eine NOTIFYICONDATA Struktur, welche folgende Elemente hat:
  • cbSize Die Größe der Struktur.
  • hwnd Handle des Fensters, welche die Benachrichtigung erhält, wenn ein Maus-Ereignis über dem Tray Icon auftritt.
  • uID Eine Konstante die als Icon Identifizierer benutzt wird. Sie sind derjenige der über den Wert entscheided. Für den Fall, dass Sie mehr als ein Tray Icon haben, sind Sie in der Lage zu überprüfen, welches Tray Icon die Maus-Benachrichtigung schickt.
  • uFlags Spezifiziert welches Element der Struktur gültig ist
    • NIF_ICON Das hIcon Element ist gültig.
    • NIF_MESSAGE Das uCallbackMessage Element ist gültig.
    • NIF_TIP Das szTip Element ist gültig.
  • uCallbackMessage Die benutzerdefinierte Nachricht, die Windows an das Fenster sendet, welches im hwnd Element spezifiziert ist, wenn ein Maus-Ereigniss über dem Tray Icon auftritt. Sie erzeugen diese Nachricht selbst.
  • hIcon Das Handle des Icons, das Sie im System Tray plazieren wollen.
  • szTip Ein 64-Byte Array das den String enthält, welcher als Tooltip-Text benutzt wird, wenn die Maus sich über dem Tray Icon befindet.
  • Rufen Sie Shell_NotifyIcon auf, welche in der shell32.inc definiert ist. Diese Funktion hat folgenden Prototyp:
  • Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD


    dwMessage ist die Art der Nachricht, welche an die Shell gesendet wird.
    NIM_ADD Fügt ein Icon dem Status-Bereich hinzu.
    NIM_DELETE Löscht ein Icon aus dem Status-Bereich.
    NIM_MODIFY Modifiziert ein Icon im Status-Bereich.
    pnid ist eine Zeiger auf eine NOTIFYICONDATA Struktur, gefüllt mit den entsprechenden Werten.
    Wenn Sie ein Icon dem Tray hinzufügen wollen, benutzen Sie die NIM_ADD Nachricht, wenn Sie das Icon entfernen wollen, benutzen Sie NIM_DELETE. Das ist alles was Sie tun müssen. Aber in der Regel werden Sie nicht zufrieden sein, einfach nur ein Icon dort zu plazieren. Sie müssen in der Lage sein, auf Maus-Ereignisse über dem Tray Icon zu reagieren. Sie können das tun, indem Sie die Nachricht, die Sie im uCallbackMessage Element der NOTIFYCONDATA Struktur spezifiziert haben, bearbeiten. Die Nachricht hat folgende Werte in wParam und lParam (besonderen Dank an s__d für die Information):
    • wParam enthält die ID des Icons. Das ist der selbe Wert, den Sie im uID Element der NOTIFYICONDATA Struktur angegeben haben.
    • lParam Das untere Word enthält die Maus-Nachricht. Wenn der Benutzer zum Beispiel ein rechts-Klick auf das Icon gemacht hat, enthält lParam WM_RBUTTONDOWN.
    Die meisten Tray Icons zeigen ein Popup-Menü an, wenn der Benutzer einen rechts-Klick tätigt. Wir können dieses Feature implementieren, indem wir ein Popup-Menü erzeugen und dann TrackPopupMenu aufrufen, um es anzuzeigen. Die Schritte werden folgend beschrieben:
    1. Erzeugen Sie ein Popup-Menü, indem Sie CreatePopupMenu aufrufen. Diese Funktion erzeugt ein leeres Menü. Sie liefert das Menü-Handle in EAX zurück, wenn sie erfolgreich war.
    2. Fügen Sie Menü-Elemente mit AppendMenu, InsertMenu oder InsertMenuItem hinzu.
    3. Wenn Sie das Popup-Menü an der Stelle anzeigen wollen, wo sich der Mauscursor befindet, rufen Sie getCursorPos auf, um die Bildschirm-Koordinaten des Cursurs zu ermitteln und rufen dann TrackPopupMenu auf, um das Menü anzuzeigen. Wenn der Benutzer ein Menü-Element aus dem Popup-Menü wählt, sendet Windows eine WM_COMMAND Nachricht an Ihre Fenster-Prozedur, wie eine normale Menü-Auswahl.
    Anmerkung: Seien Sie vor zwei ärgerlichen Verhaltensweisen gewarnt, wenn Sie ein Popup-Menü mit einem Tray-Icon benutzen:
    1. Wenn das Popup-Menü angezeigt wird und Sie außerhalb des Menüs irgendwo hinklicken, verschwindet das Popup-Menü nicht unverzüglich, so wie es sollte. Dieses Verhalten rührt daher, da das Fenster, das die Benachrichtigung vom Popup-Menü erhält das Fenster im Vordergrund sein MUSS. Rufen Sie einfach SetForegroundWindow auf und die Sache ist gegessen.
    2. Nach dem Aufruf von SetForegroundWindow, werden Sie das erste Mal, wenn das Popup-Menü angezeigt wird, feststellen, dass es korrekt läuft, aber die darauffolgende Male, wird das PopUp-Menü angezeigt und sofort wieder geschlossen. Dieses Verhalten ist "beabsichtigt", um die MSDN zu zitieren. Der Task Switch zu dem Programm, das der Besitzer des Tray Icons ist, ist in naher Zukunft nötig. Sie können diesen Task Switch erzwingen, indem Sie irgend eine Nachricht an das Fenster des Programms senden. Benutzen Sie einfach PostMessage, nicht SendMessage!

    Beispiel:

    .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:

    Das Programm zeigt ein einfaches Fenster an. Wenn Sie den Minimierungs-Button klicken, wird sich Programm selbst verstecken un ein Icon im System Tray plazieren. Wenn Sie einen Doppelklick auf das Icon machen, wird das Programm wieder angezeigt und das Icon aus dem System Tray entfernt. Wenn Sie einen Rechtsklick machen, wird ein Popup-Menü angezeigt. Sie können dann auswählen, ob das Programm wieder angezeigt oder beendet werden soll.
    .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


    Wenn das Hauptfenster erzeugt wurde, wird das Popup-Menü erzeugt und zwei neue Elemente angehängt. AppendMenu hat folgende Syntax:
    AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD



    • hMenu ist das Handle des Menüs, zu dem Sie das Element hinzufügen möchten.
    • uFlags teilt Windows mit, wie das Menü-Element hinzugefügt werden soll und welcher Art es ist, ob es ein Bitmap oder ein String oder ein eigenes gezeichnetes Element ist, aktiviert, gegraut, deaktiviert, etc. Sie können die komplette List aus der Win32-API-Referenz bekommen. In unserem Beispiel benutzen wir MF_STRING, was bedeutet, dass das Menü-Element ein String ist.
    • uIDNewItem ist die ID des Menü-Elements. Das ist ein benutzerdefinierter Wert, der benutzt wird, um das Menü-Element zu repräsentieren.
    • lpNewItem spezifiziert den Inhalt des Menü-Elements, abhängig davon, was Sie im uFlags-Element angegeben haben. Da wir MF_STRING im uFlags-Element angegeben haben, muss lpNewItem einen Zeiger auf den String enthalten, der in dem Popup-Menü angezeigt werden soll.
    Nachdem das Popup-Menü erzeugt wurde, wartet das Hauptfenster geduldig darauf, dass der Benutzer den Minimierungs-Button drückt.
    Wenn ein Fenster minimiert ist, erhält es die WM_SIZE-Nachricht mit SIZE_MINIMIZED als Wert in 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


    Wir benutzen diese Gelegenheit um die NOTIFYCONDATA Struktur zu füllen. IDI_TRAY ist nur eine Konstante, die am Anfang des Source Codes definiert wurde. Sie können sie auf jeden beliebigen Wert setzen. Er ist nicht wichtig, da Sie nur ein Tray-Icon haben. Wenn Sie aber mehrere Icons im System Tray plazieren, brauchen Sie eine eindeutige ID für jedes Tray Icon. Wir spezifizieren allen Flags im uFlags-Element, da wie ein Icon (NIF_ICON) spezifizieren, wir spezifizieren eine eigene Nachricht (NIF_MESSAGE) und wir spezifizieren einen Tooltip-Text (NIF_TIP). WM_SHELLNOTIFY ist nur eine eigene Nachricht, definiert als WM_USER+5. Der aktelle Wert ist nicht weiter wichtig, so lange er eindeutig ist. Ich benutze das Windows-Logo-Icon als Try Icon, aber Sie können hier jedes Icon in Ihrem Programm benutzen. Laden Sie es einfach aus der Ressource mit LoadIcon und speichern Sie das zurückgelieferte Handle im hIcon-Element. Als letztes füllen wir szTip mit dem Text der von der Shell angezeigt werden soll, wenn die Maus über dem Icon ist.
    Wir verstecken das Haupt-Fenster um die Illusion "minimiert-zum-Tray-Icon"-Erscheinung zu geben.
    Als nächstes rufen wie Shell_NotifyIcon mit der NIM_ADD Nachricht auf, um das Icon dem System Tray hinzuzufügen. Nun ist unser Haupt-Fenster versteckt und das Icon ist im System Tray. Wenn Sie die Maus drüber bewegen, werden Sie einen Tooltip sehen, der den Text anzeigt, den wir im szTip-Element angegeben haben. Als nächstes, wenn Sie einen Doppelklick auf das Icon machen, wird das Haupt-Fenster wieder erscheien und das Tray Icon verschwinden.
    .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


    Wenn ein Maus Ereigniss über den Tray Icon auftritt, erhält Ihr Fenster die WM_SHELLNOTIFY Nachricht, welche die benutzerdefinierte Nachricht ist, die Sie im uCallbackMessage-Element definiert haben. Erinnern Sie sich, dass wParam die Tray Icon ID und lParam die aktuelle Maus-Nachricht enthält, wenn Sie diese Nachricht erhalten. Im oberen Code überprüfen wir, ob diese Nachricht vom Tray Icon kommt, an dem wir interessiert sind. Wenn ja, überprüfen wir die aktuelle Maus-Nachricht. Da wir nur an einem Rechts-Maus-Klick und einem Doppelklick interessiert sind, bearbeiten wir nur WM_RBUTTONDOWN und WM_LBUTTONDBLCLK Nachrichten.
    Wenn die Maus-Nachricht WM_RBUTTONDOWN ist, rufen wir GetCursorPos auf, um die aktuellen Bildschirm-Koordinaten der Maus zu erhalten. Wenn die Funktion zurückkehrt, ist die POINT Struktur mit den Bildschirm-Koordinaten des Maus-Cursors gefüllt. Mit Bildschirm-Koordinaten meine ich den geesanten Bildschirm ohne irgendwelche Fenster-Begrenzungen. Wenn die Bildschirmauflösung zum Beispiel 640*480 ist, ist die rechte untere Ecke des Bildschirms x==639 und y==479 ist. Wenn Sie die Bildschirm-Koordinaten in Fenster-Koordinaten umwandeln wollen, benutzen Sie die ScreenToClient Funktion.
    Wie dem auch sei, für unser Vorhaben, wir wollen das Popup-Menü an der aktuellen Maus-Cursor-Position mit einem TrackPopupMenu-Aufruf anzeigen, benötigen wir Bildschirm-Koordinaten. Wir können die Koordinaten, die von GetCursorPos gegeben sind, direkt benutzen.
    TrackPopupMenu hat folgende Syntax:
      TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD



    • hMenu ist das Handle des Popup-Menü, dass angezeigt werden soll.
    • uFlags spezifiziert die Optionen der Funktion. So wie wo die Position des Menüs relativ zu später spezifizierten Koordinaten sein soll und welcher Maus-Button benutzt wird, um das Menü anzuzeigen. In unserem Beispiel benutzen wir TPM_RIGHTALIGN, um das Popup-Menü links von den Koordinaten zu positionieren.
    • x und y spezifizieren den Ort des Menüs in Bildschirm-Koordinaten.
    • nReserved muss NULL sein.
    • hWnd ist das Handle des Fensters, welches die Nachrichten für das Menü empfängt.
    • prcRect ist das Rechteck im Bildschirm, wo es möglich ist, zu klicken ohne dass das Menü verschwindet. Normalerweise geben wir hier NULL an, so dass wenn der Benutzer irgendwo außerhalb des Menüs hinklickt, dass das Menü verschwindet.
    Wenn der Benutzer ein Doppelklick auf das Tray Icon macht, senden wir eine WM_COMMAND Nachricht mit IDM_RESTORE an unser eigenes Fenster um ein Klick auf das Restore-Menü-Element zu emulieren, so dass das Haupt-Fenster wieder hergestellt wird und das Icon aus dem System Tray genommen wird. Damit Doppelklick-Nachrichten auch an das Fenster gesendet werden können, muss das Fenster den CS_DBLCLKS Stil haben.
    invoke Shell_NotifyIcon,NIM_DELETE,addr note mov eax,wParam .if ax==IDM_RESTORE invoke ShowWindow,hWnd,SW_RESTORE .else invoke DestroyWindow,hWnd .endif


    Wenn der Benutzer das Restore Menü-Element auswählt, entfernen wir das Tray Icon in dem wir Shell_NotifyIcon wieder aufrufen, diesmal mit der NIM_DELETE Nachricht. Als nächstes stellen wir das Haupt-Fenster wieder in seinem Original Zustand her. Wenn der Benutzer das Exit Menü-Element auswählt, entfernen wir auch das Tray Icon und zerstören das Haupt-Fenster in dem wir DestroyWindow aufrufen.

    Deutsche Übersetzung: Joachim Rohde
    Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage