系統(tǒng)托盤中的快捷圖標

字號:

本課中,我們將學習如何把小圖標放到系統(tǒng)托盤中去以及如何創(chuàng)建和使用彈出式菜單。
    理論:
    系統(tǒng)托盤是指任務條中的一個方形區(qū)域,在該區(qū)域中可以放入一些小圖標,通常您可以在此處看到系統(tǒng)提供的新時間。您自己當然也可以把快捷小圖標放到此處。下面是這么做的步驟:
    設置NOTIFYICONDATA型的結(jié)構(gòu)體變量的成員變量的值:
    cbSize 該結(jié)構(gòu)體的大小。
    hwnd 窗口的句柄。當鼠標滑過該小圖標時,該窗口將接收到相關的消息。
    uID 小圖標的ID號。您可以取任意值,只是當您的應用程序有不止一個小圖標時,您要能夠區(qū)分出到底是那一個小圖標接收到了鼠標的消息,也即ID號必須。
    uFlags 指定該結(jié)構(gòu)體變量的那些成員變量有效。
    NIF_ICON 有效。
    NIF_MESSAGE 有效。
    NIF_TIP 有效。
    uCallbackMessage 自定義的消息。當鼠標對小圖標動作時,WINDOWS外殼將把該消息發(fā)送到您的應用程序。該消息的值您可以自己定義。
    hIcon 放入系統(tǒng)托盤中的圖標的句柄。
    szTip 64字節(jié)的緩沖區(qū),它用來放入提示字符串,當鼠標停留在小圖標上時,就會顯示該字符串。
    調(diào)用Shell_NotifyIcon函數(shù)。該函數(shù)在shell32.inc中定義,其原型如下:
    Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD
    dwMessage 是發(fā)送到WINDOWS外殼的消息:
    NIM_ADD 把小圖標加到系統(tǒng)托盤區(qū)。
    NIM_DELETE 從系統(tǒng)托盤中刪除小圖標。
    NIM_MODIFY 修改小圖標。
    pnid 是指向NOTIFYICONDATA型結(jié)構(gòu)體變量的指針。
    如果您想要加入一個小圖標就用NIM_ADD,刪除時使用NIM_DELETE消息。
    基本上的消息就是這些。但是大多數(shù)的情況下,您不會僅僅滿足把一個小圖標放到那里。您還必須要對鼠標事件作出適當?shù)姆磻?。您可以在  NOTIFYICONDATA型的結(jié)構(gòu)體變量的成員變量uCallbackMessage 中設置您要處理的消息,然后WINDOWS外殼將在發(fā)生這些事件時通知您的應用程序。隨著消息傳送的參數(shù)wParam和lParam的值如下:
    wParam 小圖標的ID號。它和您在NOTIFYICONDATA型結(jié)構(gòu)體變量中的成員變量uID中設置的值一樣。
    lParam 低字包含鼠標消息。譬如,用戶在小圖標上按下了右鍵時,lParam中將包含WM_RBUTTONDOWN消息。
    大多數(shù)的系統(tǒng)托盤中的小圖標,在用戶用鼠標右擊時都會彈出一個菜單以方便用戶選擇。我們可先創(chuàng)建菜單,然后調(diào)用TrackPopupMenu函數(shù)來顯示它。步驟如下:
    調(diào)用CreatePopupMenu函數(shù)來創(chuàng)建菜單。該函數(shù)創(chuàng)建一個空的菜單。如果成功,將在eax中返回該菜單的句柄。
    調(diào)用AppendMenu, InsertMenu 或 InsertMenuItem來向菜單中加入菜單項。
    當您想在當前鼠標位置顯示該菜單時,調(diào)用GetCursorPosition函數(shù)來得到鼠標當前的屏幕位置,然后調(diào)用TrackPopupMenu來顯示菜單。當用戶從彈出式菜單中選擇了一個菜單項時,WINDOWS將發(fā)送WM_COMMAND消息給您應用程序的消息處理過程,這和通常的菜單選擇是一樣的。.
    注意:當您使用系統(tǒng)托盤中的小圖標時有兩件比較討厭的事:
    該菜單可能不會像通常那樣馬上消失掉。這是因為從彈出式接收消息的窗口必須是前景窗口。調(diào)用SetForegroundWindow函數(shù)就可以糾正該錯誤;
    在調(diào)用了SetForegroundWindow函數(shù)后,您會發(fā)現(xiàn)第該彈出式菜單會正常彈出而且工作的很好。但是隨后,該菜單只是一彈出就立即消失。根據(jù)MSDN,這么做是故意的。為了使得彈出菜單保持住,必須要求下一個切換到的是程序的主窗口。您可以通過郵寄任何消息給該程序的窗口來強行進行任務切換。注意要使用PostMessage而不是SendMessage。
    例子:
    .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
    分析:
    該程序?qū)@示一個簡單的窗口。當您按下小化按鈕時,該窗口將隱藏,然后放一個小圖標到系統(tǒng)托盤中。當您雙擊小圖標時,應用程序?qū)⒒謴妥约?,并把小圖標從系統(tǒng)托盤中刪除。當您右擊小圖標時,會顯示一個彈出式菜單。您可以在菜單中選擇是恢復窗口還是退出應用程序。
    .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
    當主窗口創(chuàng)建時,將會創(chuàng)建一個彈出式菜單,并且加入兩個菜單項。 AppendMenu的語法如下:
    AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
    hMenu 是將要加入菜單項的菜單的句柄。
    uFlags 告訴WINDOWS要加入的菜單項是位圖、字符串或自畫的項目以及是可用、不可用或灰色顯示等。您可以從WIN32 API 指南中得到全部的標志位的信息。在我們的例子中使用標志位MF_STRING,它表示我們加入的菜單項是字符串。
    uIDNewItem 是菜單項的ID號。這是一個用戶自定義的值,它用來地代表菜單項。.
    lpNewItem 用來指定菜單項的內(nèi)容,具體代表什么取決于uFlags中指定的標志。我們前面指定了MF_STRING標志,所以此處代表一個字符串
    主窗口創(chuàng)建完成后,用戶就可以開始測試了。這時按下小化鍵。
    當一個窗口被小化時將接收到WM_SIZE消息,其中wParam參數(shù)中的值為SIZE_MINIMIZED。
    .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
    這時我們來給NOTIFYICONDATA型結(jié)構(gòu)體變量賦值。IDI_TRAY是在代碼開始處定義的一個數(shù)值常量,您可以任意設定它的值。由于我們僅有一個圖標,所以這一點并不重要,如果要同時加入幾個系統(tǒng)圖標的話,那么每個圖標都要有一個的ID號。由于我們指定了一個圖標NIF_ICON,所以我們要在uFlags成員變量中指定所有的標志位,我們還指定了一個自定義的消息NIF_MESSAGE和幫助文本NIF_TIP。  WM_SHELLNOTIFY 被定義為WM_USER+5,只要是的值,就無所謂是多少了,只要大于WM_USER。我們這里用的是WINDOWS登錄時的圖標,當然您可以使用任意您想要用的圖標,您可以用LoadIcon函數(shù)從資源中裝載,該函數(shù)返回一個圖標的句柄。后我們在szTip中放入當鼠標放在圖標時顯示的提示文本。為了達到“小化然后只顯示圖標的效果”,我們在這時隱藏掉主窗口。
    接下來,我們調(diào)用Shell_NotifyIcon函數(shù)并指定標志位NIM_ADD把圖標加到系統(tǒng)托盤中去。
    現(xiàn)在我們的主窗口隱藏了,圖標顯示在系統(tǒng)托盤中。如果您讓鼠標從圖標上滑過,將看到提示文本。如果您雙擊小圖標,主窗口就會顯示,圖標將消失。
    .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
    當在系統(tǒng)托盤中的圖標發(fā)生鼠標事件時,您的窗口將接收到WM_SHELLNOTIFY消息,該消息是在uCallbackMessage成員變量中指定的。在接收到該消息時,wParam中包含了圖標的ID號,lParam中包含了鼠標動作的原始數(shù)據(jù)。在上面的代碼中,我們首先檢測是否是我們感興趣的消息。如果是的話,我們在看看是什么消息。因為我們只對右擊和雙擊事件感興趣,所以我們僅僅處理WM_RBUTTONDOWN和WM_LBUTTONDBLCLK消息。
    如果是WM_RBUTTONDOWN,我們調(diào)用GetCursorPos來得到鼠標光標所在的當前屏幕位置。注意我指的是屏幕位置,即,其坐標是相對于整個的屏幕的。譬如,如果屏幕的解析讀640*480,那么它的右下角的坐標是x==639 ,y==479。如果您想要把屏幕位置轉(zhuǎn)換成窗口的坐標,可以調(diào)用ScreenToClient函數(shù)
    我們想要在當前的位置顯示彈出式菜單,我們就調(diào)用TrackPopupMenu函數(shù),該函數(shù)需要屏幕的坐標,由GetCursorPos函數(shù)返回的坐標就可以原封不動的拿過來用。
    TrackPopupMenu的原型如下:
    TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD,  prcRect:DWORD
    hMenu 是彈出式菜單的句柄。
    uFlags 功能的選擇。像在哪里放置(相對于隨后將指定的坐標)菜單,那一個鼠標按鈕用來跟蹤彈出式菜單。在我們的例子中,我們用  TPM_RIGHTALIGN標志位來指定彈出式菜單放在坐標的左邊。
    x 和 y 指定放置菜單的屏幕坐標。
    nReserved 必須為NULL。
    hWnd 是將要接收消息的窗口的句柄。
    prcRect 指定一個矩形區(qū)域。如果在該矩形區(qū)域外面按下鼠標的話,菜單將消失。一般我們把該值設為NULL,這樣當用戶只要在菜單外面按下鼠標,菜單立即消失。
    當用戶雙擊圖標時,我們給我們自己的窗口發(fā)送WM_COMMAND消息,并指定消息為IDM_RESTORE,這樣可以達到和在彈出式菜單中選擇“Restore”菜單項同樣的效果。為了能夠接收到雙擊消息,主窗口必須要有的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
    當用戶選擇恢復主窗口時,我們調(diào)用Shell_NotifyIcon函數(shù)來刪除掉系統(tǒng)托盤中的圖標,這我們要指定NIM_DELETE消息。接下來我們把主窗口恢復到原始的狀態(tài)。如果用戶選擇了Exit菜單項,我們不但把圖標給刪除掉,也從整個的應用程序中退出。