系統托盤位於工作列通知區域,常駐程式(防毒、雲端同步、輸入法)多利用它提供快速存取入口。本文完整示範:建立托盤圖示、處理滑鼠事件、建立彈出式選單、最小化至托盤/從托盤還原 的完整流程與陷阱規避。
NOTIFYICONDATA STRUCT
cbSize dd ? ; 結構大小
hwnd dd ? ; 接收回呼訊息的視窗控制代碼
uID dd ? ; 圖示識別碼(多圖示時區分)
uFlags dd ? ; 指定哪些欄位有效:NIF_ICON | NIF_MESSAGE | NIF_TIP
uCallbackMessage dd ? ; 自訂回呼訊息(如 WM_USER+5)
hIcon dd ? ; 托盤圖示控制代碼
szTip db 64 dup(?) ; 滑鼠停留顯示的提示文字
NOTIFYICONDATA ENDS
Shell_NotifyIcon PROTO dwMessage:DWORD, pnid:DWORD
; dwMessage: NIM_ADD (加入), NIM_DELETE (移除), NIM_MODIFY (修改)
; pnid: NOTIFYICONDATA 結構指標
當滑鼠在托盤圖示上動作時,系統將 uCallbackMessage 發送給 hwnd:
wParam = uID(圖示 ID)lParam 低字 = 原始滑鼠訊息(WM_LBUTTONDBLCLK, WM_RBUTTONDOWN 等)CreatePopupMenu 建立空選單,取得 hMenu。AppendMenu(hMenu, MF_STRING, IDM_ITEM, "文字") 加入項目。GetCursorPos 取得螢幕座標 → SetForegroundWindow(hwnd) 確保視窗為前景(否則選單不會自動消失) → TrackPopupMenu(hMenu, TPM_RIGHTALIGN, x, y, 0, hwnd, NULL) 顯示選單 → PostMessage(hwnd, WM_NULL, 0, 0) 強制工作切換修正選單立即消失的已知問題。.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 ; IDM_EXIT
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
WM_SIZE + wParam == SIZE_MINIMIZED → 填寫 NOTIFYICONDATA → ShowWindow(hWnd, SW_HIDE) 隱藏主視窗 → Shell_NotifyIcon(NIM_ADD, ...) 圖示出現在托盤。
WM_SHELLNOTIFY + lParam == WM_RBUTTONDOWN → 取得滑鼠螢幕座標 → SetForegroundWindow + TrackPopupMenu + PostMessage(WM_NULL) 顯示選單並修正自動消失 Bug。WM_COMMAND + IDM_RESTORE 觸發還原邏輯。WM_COMMAND + lParam == 0 表示來自選單。先 NIM_DELETE 移除托盤圖示,再依 wParam 判斷還原視窗或銷毀程式。
TrackPopupMenu 前呼叫 SetForegroundWindow(hwnd) 確保視窗為前景。TrackPopupMenu 後緊接 PostMessage(hwnd, WM_NULL, 0, 0) 強制發送一個空訊息觸發工作切換。uID,WM_SHELLNOTIFY 中依 wParam 分派。Shell_NotifyIcon(NIM_MODIFY, ...) 更新 hIcon 或 szTip。NOTIFYICONDATA 擴充版 (Shell32.dll 5.0+) 支援 NIF_INFO、szInfo、szInfoTitle、dwInfoFlags。InsertMenuItem + MIIM_BITMAP/MIIM_OWNERDRAW。系統托盤圖示是長駐型應用程式的標準交互入口。掌握 NOTIFYICONDATA、Shell_NotifyIcon、自訂回呼訊息、彈出式選單建立與 TrackPopupMenu 的兩個經典 Bug 修正,即可在組合語言中實作專業級的托盤互動功能。