全部博文(290)
分类:
2008-02-27 15:32:16
Tutorial 23: Tray Icon
第二十三课:托盘图标
In this tutorial, we will learn how to put icons into system tray and how to create/use a popup menu.
在这一课中,我们将学习如何把一个图标放入系统托盘以及学习如何创建和使用一个弹出式菜单。
Download the example here.
Theory:
原理:
System tray is the rectangular region in the taskbar where several icons reside. Normally, you'll see at least a digital clock in it. You can also put icons in the system tray too. Below are the steps you have to perform to put an icon into the system tray:
系统托盘是在任务栏上可以驻留若干图标的一块矩形区域。通常,你至少可以在它上面看到一个数字显示式时钟。你也能放置一些图标在系统托盘上。你必须执行下面这些步骤来放置一图标在系统托盘上:
填充一个 NOTIFYICONDATA结构,这个结构的成员如下:
作为图标标识的一常数。你可以取任意值,但是值必须是唯一的。因为,当有多个图标在托盘上时,你将要区分鼠标消息来自于那个图标。故这个值必须取唯一值。
指定这个结构的那些成员变量有效。
hIcon 成员有效
uCallbackMessage 成员有效
SzTip 成员有效
uCallbackMessage 自定义消息。当鼠标事件在托盘图标上发生时,windows将通过hwnd成员发送给指定的窗口。你可以自己创建这个消息。
hIcon 你想放在系统托盘上的图标的句柄。
SzTip 一个64位的数组,这个数组存放的是---当鼠标停留在小图标上时,作为提示文本的字符串。
调用Shell_NotifyIcon函数,这个还属定义在shell32.inc中.函数原型如下:
Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD
dwMessage is the type of message to send to the shell.
DwMessage 是发送给windows外壳的消息.
NIM_ADD Adds an icon to the status area.
往状态栏上增加一个图标.
NIM_DELETE Deletes an icon from the status area.
从状态栏上删除一图标.
NIM_MODIFY Modifies an icon in the status area.
在状态栏中修改一图标.
pnid is the pointer to a NOTIFYICONDATA structure filled with proper values
Pnid 是一个指向填满特征值的NOTIFYICONDATA 结构指针。
If you want to add an icon to the tray, use NIM_ADD message, if you want to remove the icon, use NIM_DELETE.
如果你想增加一个图标在托盘上,用NIM_ADD 消息,如果你想移出一个图标,用NIM_DELETE消息。
That's all there is to it. But most of the time, you're not content in just putting an icon there. You need to be able to respond to the mouse events over the tray icon. You can do this by processing the message you specified in uCallbackMessage member of NOTIFYICONDATA structure. This message has the following values in wParam and lParam (special thanks to s__d for the info):
基本上全部的消息就是这些。但是大多数时候,你并不满足于仅仅放置一图标在这儿。你需要在鼠标事件发生在托盘图标上时能够做出响应。你能够这样做,通过在NOTIFYICONDATA结构中的uCallbackMessage成员中指定你要处理的消息。这个消息的wparam和lparam的值如下。
Wparam 包含这个图标的ID号。这是一个你放在NOTIFYICONDATA结构成员uID的值。
Lparam 低字节包含鼠标消息。例如,如果你在图标上点击了右键,lparam将包含WM_RBUTTONDOWN消息。
Most tray icon, however, displays a popup menu when the user right-click on it. We can implement this feature by creating a popup menu and then call TrackPopupMenu to display it. The steps are described below:
然而,当用户在托盘图标上点击右键时,大多数的托盘图标将显示一个弹出式菜单。我们能通过创建一个弹出式菜单来实现这一特征,然后调用TrackPopupMenu来显示它。这些步骤描述如下:
通过调用CreatePopupMenu函数来创建一弹出式菜单。这个函数创建一个空的菜单。如果成功,它在eax中返回一个菜单句柄。
用AppendMenu, InsertMenu 或InsertMenuItem函数来添加菜单项。
当你想在鼠标光标处显示这个菜单时,调用GetCursorPos函数来得到光标的屏幕坐标,然后调用TrackPopMenu来显示菜单。当用户从弹出式菜单上选择一个菜单项时,windows发送WM_COMMAND消息给你的窗口处理过程,就像你选择了正常的菜单一样。
Note: Beware of two annoying behaviors when you use a popup menu with a tray icon:
注意:当你在托盘图标上使用一个弹出菜单时,小心两件比较恼人的行为。
当弹出式菜单被显示时,如果你在菜单以外任何地方点击了鼠标,这个弹出式菜单不会像它应该做的那样立即消失不见。这个行为重现是因为接收菜单消息的窗口必须是前景窗口。仅需调用SetForegroundWindow就能纠正它。
在调用SetForegroundWindow函数后,你将发现第一次弹出的菜单正常显示并且工作的很好,但是在后来,弹出式菜单弹出便立即关闭。引用MSDN这样的行为是“故意的”。为了使得弹出菜单保持住,必须要求下一个切换到的是程序的主窗口。你能通过置入任何消息给程序的窗口来强制的切换任务。但是只能用PostMessage函数而不能用SendMessage函数。
Example:
.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
Analysis:
分析:
The program will display a simple window. When you press the minimize button, it will hide itself and put an icon into the system tray. When you double-click on the icon, the program will restore itself and remove the icon from the system tray. When you right-click on it, a popup menu is displayed. You can choose to restore the program or exit it.
这个程序将显示一个简单的窗口。当你按最小化按钮时,它将隐藏它自己并且放置一图标到系统托盘中。当你双击这个图标时,这个程序将恢复它原来的窗口并且将图标从系统托盘中移出。当你用右键点击它时,一个弹出式菜单被显示。你能选择恢复这个程序或者是退出程序。
.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
When the main window is created, it creates a popup menu and append two menu items. AppendMenu has the following syntax:
当主窗口被创建时,它创建一个弹出式菜单并且添加两个菜单项。AppendMenu句法如下:
AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
· hMenu is the handle of the menu you want to append the item to
hMenu是你想创建的那个菜单项的句柄。
· uFlags tells Windows about the menu item to be appended to the menu whether it is a bitmap or a string or an owner-draw item, enabled, grayed or disable etc. You can get the complete list from win32 api reference. In our example, we use MF_STRING which means the menu item is a string.
uFlags 告诉windows添加的这个菜单项是一个位图或者字符串还是以个自画的项,以及它是否可用,变灰还是禁用等。你能从win32API手册中得到完全清单。在我们的例子中,我们用MF_STRING, 它意味着这个菜单项是字符串.
· uIDNewItem is the ID of the menu item. This is a user-defined value that is used to represent the menu item.
uIdNewItem 是菜单项的ID号.这是一个用户自定义的值,用这个值来表示这个菜单项.
· lpNewItem specifies the content of the menu item, depending on what you specify in uFlags member. Since we specify MF_STRING in uFlags member, lpNewItem must contain the pointer to the string to be displayed in the popup menu.
LpNewItem 指定菜单项的内容,依赖于uFlags成员中的值.因为我们指定在uFlags中的值是MF_STRING,lpNewItem必须包含一个指针,这个指针指向显示在弹出菜单中的字符串.
After the popup menu is created, the main window waits patiently for the user to press minimize button.
在弹出式菜单被创建之后,主窗口耐心的等待用户按下最小化按钮.
When a window is minimized, it receives WM_SIZE message with SIZE_MINIMIZED value in wParam.
当窗口被最小化时,它接收到WM_SIZE消息,其中wParam参数的值是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
We use this opportunity to fill NOTIFYICONDATA structure. IDI_TRAY is just a constant defined at the beginning of the source code. You can set it to any value you like. It's not important because you have only one tray icon.But if you will put several icons into the system tray, you need unique IDs for each tray icon.
我们用这个时机来填充NOTFYICONDATA结构体。IDI_TRAY仅是一个在源代码开始处定义的常数。你可以用你喜欢的任何值来设置它。它并不是很重要,因为你只有一个托盘图标。但是,如果你放置几个图标在系统托盘中,你需要为每一个图标定义不同的ID号。
We specify all flags in uFlags member because we specify an icon (NIF_ICON), we specify a custom message (NIF_MESSAGE) and we specify the tooltip text (NIF_TIP). WM_SHELLNOTIFY is just a custom message defined as WM_USER+5. The actual value is not important so long as it's unique. I use the winlogo icon as the tray icon here but you can use any icon in your program. Just load it from the resource with LoadIcon and put the returned handle in hIcon member. Lastly, we fill the szTip with the text we want the shell to display when the mouse is over the icon. We hide the main window to give the illusion of "minimizing-to-tray-icon" appearance.
我们在uFlags成员中设置所有的标置,因为我们指定了一个图标(NIF_ICON),我们还指定了一个自定义的消息NIF_MESSAGE和帮助文本NIF_TIP。WM_SHELLNOTIFY仅是一个定义为WM_USER+5 的自定义消息。这个实际的值并不重要,只要它是唯一的值。 在这里,我们使用的是windows登录时的图标,但是你也可以在你的程序中使用任何图标。仅需用LoadIcon函数从资源中撞在它并且把它返回的值放在hIcon成员中。最后,我们用文本填充szTip的值,这个文本的值是当鼠标在图标上时我们要显示的字符串。为了达到“最小化然后只显示图标的效果”,我们在这时隐藏掉主窗口
Next we call Shell_NotifyIcon with NIM_ADD message to add the icon to the system tray.
接下来,我们调用Shell_NotifyIcon 函数并用NIM_ADD消息来添加一个图标到系统托盘中。
Now our main window is hidden and the icon is in the system tray. If you move the mouse over it, you will see a tooltip that displays the text we put into szTip member. Next, if you double-click at the icon, the main window will reappear and the tray icon is gone.
现在我们的主窗口式隐藏的并且这个图标也在系统托盘中。如果你把鼠标移到它上面,你将看到我们放入szTip成员中的提示文本。下一步,如果你双击这个图标,主窗口将再次出现并且托盘图标消失 。
.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
When a mouse event occurs over the tray icon, your window receives WM_SHELLNOTIFY message which is the custom message you specified in uCallbackMessage member. Recall that on receiving this message, wParam contains the tray icon's ID and lParam contains the actual mouse message. In the code above, we check first if this message comes from the tray icon we are interested in. If it does, we check the actual mouse message. Since we are only interested in right mouse click and double-left-click, we process only WM_RBUTTONDOWN and WM_LBUTTONDBLCLK messages.
当一个鼠标事件发生在托盘图标上时,你的窗口将接收到WM_SHELLNOTIFY消息,这个消息是你放在uCallbackMessage成员变量中的自定义消息。在收到这个消息时,wparam参数包含托盘图标的ID号,lParam包含实际的鼠标消息。在上面的代码中,我们首先检查这个消息是否来自于我们感兴趣的托盘图标,如果它是,我们检查实际的鼠标消息。由于,我们仅仅对右键和双击事件感兴趣,所以我们仅处理WM_RBUTTONDOWN和WM_LBUTTONDBLCLK消息。
If the mouse message is WM_RBUTTONDOWN, we call GetCursorPos to obtain the current screen coordinate of the mouse cursor. When the function returns, the POINT structure is filled with the screen coordinate of the mouse cursor. By screen coordinate, I mean the coordinate of the entire screen without regarding to any window boundary. For example, if the screen resolution is 640*480, the right-lower corner of the screen is x==639 and y==479. If you want to convert the screen coordinate to window coordinate, use ScreenToClient function.
如果鼠标消息是WM_RBUTTONDOWN,我们调用GetCursorPos函数来得到鼠标光标在屏幕上的坐标。当函数返回时,POINT结构填充的是鼠标光标的屏幕坐标。屏幕坐标,我意思是指没有包含任何窗口边界的整个屏幕坐标。例如,如果屏幕分辨率是640*480,屏幕右下角的坐标是x=639 y=479,如果你想屏幕坐标为窗口坐标,用ScreenToClient函数。
However, for our purpose, we want to display the popup menu at the current mouse cursor position with TrackPopupMenu call and it requires screen coordinates, we can use the coordinates filled by GetCursorPos directly.
TrackPopupMenu has the following syntax:
然而,我们的目的是,我们想在当前鼠标光标的位置弹出一菜单,所以我们调用TrackPopupMenu函数,而这个函数需要的是屏幕坐标值,我们能将GetCursorPos函数返回的坐标值直接用在TrackPopupMenu函数中,句法如下:
TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
hMenu是将要被显示的弹出菜单的句柄。
uFlags 指定一个功能选项。像相对于随后指定的坐标值,菜单放的位置和那一个鼠标按钮将被用于菜单轨迹。在我们的例子中,我们用TPM_RIGHTALIGN来决定弹出式菜单放在坐标的左边。
X 和y 指定在屏幕坐标中菜单的位置。
nReserved 必须为空
hWnd 是接受从菜单接收消息的窗口句柄。
PrcRect 是屏幕中的一块矩形。如果在该矩形区域外面按下鼠标的话,菜单将消失。一般我们把该值设为NULL,这样当用户只要在菜单外面按下鼠标,菜单立即消失。
When the user double-clicks at the tray icon, we send WM_COMMAND message to our own window specifying IDM_RESTORE to emulate the user selects Restore menu item in the popup menu thereby restoring the main window and removing the icon from the system tray. In order to be able to receive double click message, the main window must have CS_DBLCLKS style.
当用户在托盘图标上双击时,我们发送WM_COMMAND消息给我们的自己的窗口,并指定IDM_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
When the user selects Restore menu item, we remove the tray icon by calling Shell_NotifyIcon again, this time we specify NIM_DELETE as the message. Next, we restore the main window to its original state. If the user selects Exit menu item, we also remove the icon from the tray and destroy the main window by calling DestroyWindow.
当用户用选择恢复菜单项时,我们再一次调用Shell_NotifyIcon函数移出托盘图标,这个时候我们指定NIM_DELETE作为消息,下一步,我们恢复主窗口到原来的状态。如果用户选择退出菜单项,我们也从托盘中移出图标并且调用DestroyWindow来销毁窗口。
This article come from Iczelion's asm page
风向改变翻译于