Tutorial 22: SuperClassing | Tutorial 23: Tray Icon | Tutorial 24: Windows Hooks |
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.
Shell_NotifyIcon PROTO dwMessage:DWORD, pnid:DWORD
.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
.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 PROTO hMenu:DWORD, uFlags:DWORD,
uIDNewItem:DWORD, lpNewItem:DWORD
After the popup menu is created, the main window waits patiently
for the user to press minimize button.
Whena window is minimized, it receives WM_SIZE message with
SIZE_MINIMIZED value 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
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. 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.
Next we call Shell_NotifyIcon with NIM_ADD message
to add the icon to the system tray.
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.
.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.
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.
TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, \
nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
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.
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.
Tutorial 22: SuperClassing | Overview | Tutorial 24: Windows Hooks |