首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

Win32 組合語言系統托盤圖示與彈出式選單實作

     編輯:組合語言

前言

系統托盤位於工作列通知區域,常駐程式(防毒、雲端同步、輸入法)多利用它提供快速存取入口。本文完整示範:建立托盤圖示、處理滑鼠事件、建立彈出式選單、最小化至托盤/從托盤還原 的完整流程與陷阱規避。

核心結構:NOTIFYICONDATA

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

關鍵 API:Shell_NotifyIcon

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 等)

彈出式選單建立與顯示

  1. CreatePopupMenu 建立空選單,取得 hMenu。
  2. AppendMenu(hMenu, MF_STRING, IDM_ITEM, "文字") 加入項目。
  3. 右鍵點擊時: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

關鍵邏輯解析

1. 最小化時加入托盤

WM_SIZE + wParam == SIZE_MINIMIZED → 填寫 NOTIFYICONDATA → ShowWindow(hWnd, SW_HIDE) 隱藏主視窗 → Shell_NotifyIcon(NIM_ADD, ...) 圖示出現在托盤。

2. 托盤圖示互動

  • 右鍵:WM_SHELLNOTIFY + lParam == WM_RBUTTONDOWN → 取得滑鼠螢幕座標 → SetForegroundWindow + TrackPopupMenu + PostMessage(WM_NULL) 顯示選單並修正自動消失 Bug。
  • 雙擊左鍵:發送 WM_COMMAND + IDM_RESTORE 觸發還原邏輯。

3. 選單命令處理

WM_COMMAND + lParam == 0 表示來自選單。先 NIM_DELETE 移除托盤圖示,再依 wParam 判斷還原視窗或銷毀程式。

4. 兩個已知問題的解決方案

  1. 選單不自動消失:必須在 TrackPopupMenu 前呼叫 SetForegroundWindow(hwnd) 確保視窗為前景。
  2. 選單彈出即消失:MSDN 文件化行為。解法:TrackPopupMenu 後緊接 PostMessage(hwnd, WM_NULL, 0, 0) 強制發送一個空訊息觸發工作切換。

進階應用提示

  • 多圖示:每個圖示使用唯一 uID,WM_SHELLNOTIFY 中依 wParam 分派。
  • 動態修改圖示/提示:Shell_NotifyIcon(NIM_MODIFY, ...) 更新 hIcon 或 szTip。
  • 氣球提示 (Balloon Tip):NOTIFYICONDATA 擴充版 (Shell32.dll 5.0+) 支援 NIF_INFO、szInfo、szInfoTitle、dwInfoFlags。
  • 自訂右鍵選單圖示/擁有者繪製:使用 InsertMenuItem + MIIM_BITMAP/MIIM_OWNERDRAW。

總結

系統托盤圖示是長駐型應用程式的標準交互入口。掌握 NOTIFYICONDATA、Shell_NotifyIcon、自訂回呼訊息、彈出式選單建立與 TrackPopupMenu 的兩個經典 Bug 修正,即可在組合語言中實作專業級的托盤互動功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved