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:
-
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:
-
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.
-
Fügen Sie Menü-Elemente mit AppendMenu,
InsertMenu
oder InsertMenuItem hinzu.
-
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:
-
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.
-
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