1. Open the new topics page of Dream In Code in your default webbrowser.
2. Start traffic lights - will cycle through red, yellow and green icons and change the menu to stop traffic lights.
3. Exit
We will create a window, not a "normal" window but a "Message only" window since this sample does not need a window.
Windows 7 (and maybe above and vista) might not show the tray icon, click the up arrow in the tray to show hidden icons and choose customize. Then choose Tray Icon.exe (This app) and select show icons and notifications.
Let's begin! I don't know about you, but I do not want a new icon to show up in the tray each time I "run" my app, so we need a way to see if our app is already running and if it is, don't allow the new app to start. Here is how I do it, You can also use FindWindow or CreateSemaphore.
TrayIcon:
invoke CreateMutex, NULL, FALSE, offset szMutex
mov hMutex, eax
invoke GetLastError
cmp eax, ERROR_ALREADY_EXISTS
jne GoAhead
invoke MessageBox, HWND_DESKTOP, offset szRunning, NULL, MB_ICONEXCLAMATION
jmp Done
GoAhead:
call StartUp
Done:
invoke CloseHandle, hMutex
invoke ExitProcess, eax
First thing we do is create a "global" mutex in the 1,2Kernel Object Namespace. CreateMutex always returns a handle to a mutex object even if it already exists. If it exists, GetLastError will return ERROR_ALREADY_EXISTS we check for that and if we get it, we close the Mutex handle that was returned and exit the app, otherwise we continue.
StartUp proc
LOCAL msg:MSG
LOCAL wc:WNDCLASSEX
invoke memfill, addr wc, sizeof WNDCLASSEX, 0
invoke GetModuleHandle, NULL
mov hInst, eax
mov wc.cbSize, sizeof WNDCLASSEX
mov wc.hInstance, eax
mov wc.lpszClassName, offset szClsTrayTut
mov wc.lpfnWndProc, offset WndProc
invoke RegisterClassEx, addr wc
xor ecx, ecx
invoke CreateWindowEx, ecx, offset szClsTrayTut, ecx, ecx, ecx, ecx, ecx, ecx, HWND_MESSAGE, ecx, hInst, ecx
.while TRUE
invoke GetMessage, addr msg, NULL, 0, 0
.break .if !eax
invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg
.endw
mov eax, msg.message
ret
StartUp endp
Ok, so we didn't find our mutex now we have to register a window class to dispatch messages. For this example, we don't want a normal window but a 3"message only window" so I am only interested in filling in 4 members of the WNDCLASSEX structure - cbSize, lpfnWndProc, lpszClassName and hInstance. To make it easier I "zero" out the structure first with memfill.
Next we create our window to dispatch any messages windows will send to our app. Since we don't want a "normal" window all parameters except lpClassName, hWndParent, and hInstance are zero. Since it is a smaller opcode encoding to push a register, I first zero out ecx then "push" ecx instead of zero.
Once the window is created, we enter our message loop to dispatch all messages.
Now, the magic happens here in our Window Proc:
WndProc proc uses esi edi hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL pt:POINT
mov eax,uMsg
.if eax==WM_CREATE
mov esi, hInst
mov dwCurrentIcon, ICON_GREEN
mov dwTimerRunning, 0
invoke LoadImage, esi, ICON_RED, IMAGE_ICON, 0, 0, NULL
mov hRed, eax
invoke LoadImage, esi, ICON_YELLOW, IMAGE_ICON, 0, 0, NULL
mov hYellow, eax
invoke LoadImage, esi, ICON_GREEN, IMAGE_ICON, 0, 0, NULL
mov hGreen, eax
invoke LoadImage, esi, ICON_DIC, IMAGE_ICON, 0, 0, NULL
mov hDiC, eax
invoke CreatePopupMenu
mov hPopupMenu, eax
mov edi, eax
invoke AppendMenu, eax, MF_STRING, IDM_DIC_NEW, offset szDic
invoke AppendMenu, edi, MF_STRING, IDM_START, offset szStart
invoke AppendMenu, edi, MF_STRING, IDM_EXIT, offset szExit
mov nid.cbSize, sizeof NOTIFYICONDATA
push hWin
pop nid.hwnd
mov nid.uID, 0
mov nid.uFlags, NIF_ICON or NIF_MESSAGE or NIF_TIP
mov nid.uCallbackMessage, WM_SHELLNOTIFY
push hDiC
pop nid.hIcon
invoke lstrcpy, offset nid.szTip, offset szAppName
invoke Shell_NotifyIcon, NIM_ADD, offset nid
.elseif eax==WM_COMMAND
mov edx,wParam
movzx eax,dx
shr edx,16
.if edx==BN_CLICKED
.if eax == IDM_DIC_NEW
invoke ShellExecute, NULL, offset szOpen, offset szDiCNewTopics, NULL, NULL, SW_SHOWNORMAL or SW_RESTORE
.if dwTimerRunning == TRUE
mov ax, BN_CLICKED
ror eax, 16
mov ax, IDM_STOP
invoke SendMessage, hWin, WM_COMMAND, eax, NULL
push hDiC
pop nid.hIcon
invoke Shell_NotifyIcon, NIM_MODIFY, offset nid
.endif
.elseif eax == IDM_START
invoke ModifyMenu, hPopupMenu, 1, MF_BYPOSITION, IDM_STOP, offset szStop
invoke SetTimer, hWin, ID_TIMER, 500, NULL
mov dwTimerRunning, TRUE
.elseif eax == IDM_STOP
invoke ModifyMenu, hPopupMenu, 1, MF_BYPOSITION, IDM_START, offset szStart
invoke KillTimer, hWin, ID_TIMER
mov dwTimerRunning, FALSE
.elseif eax==IDM_EXIT
invoke Shell_NotifyIcon, NIM_DELETE, offset nid
invoke SendMessage, hWin, WM_CLOSE, 0, 0
.endif
.endif
.elseif eax == WM_SHELLNOTIFY
.if wParam == 0
.if lParam == WM_RBUTTONDOWN or WM_RBUTTONUP
invoke GetCursorPos, ADDR pt
invoke SetForegroundWindow, hWin
invoke TrackPopupMenuEx, hPopupMenu, TPM_LEFTALIGN or TPM_LEFTBUTTON, pt.x, pt.y, hWin, 0
invoke PostMessage, hWin, WM_NULL, 0, 0
.endif
.endif
.elseif eax == WM_TIMER
.if dwCurrentIcon == ICON_RED
push hGreen
pop nid.hIcon
mov dwCurrentIcon, ICON_GREEN
.elseif dwCurrentIcon == ICON_YELLOW
push hRed
pop nid.hIcon
mov dwCurrentIcon, ICON_RED
.else
push hYellow
pop nid.hIcon
mov dwCurrentIcon, ICON_YELLOW
.endif
invoke Shell_NotifyIcon, NIM_MODIFY, offset nid
.elseif eax==WM_CLOSE
invoke DestroyIcon, hRed
invoke DestroyIcon, hYellow
invoke DestroyIcon, hGreen
invoke DestroyIcon, hDiC
invoke DestroyMenu, hPopupMenu
invoke KillTimer, hWin, ID_TIMER
invoke DestroyWindow, hWin
.elseif eax==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWin,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
WM_CREATE
When our window is created, we set a "flag" for our starting icon to ICON_GREEN, and our Timer is not running. Then we load 4 icons from our resource section and save their handles.
Create a popup menu and save that handle.
Next we add 3 menu items to the popup menu with AppendMenu.
Next we fill out the NOTIFYICONDATA structure
cbSize = size of structure
hwnd = the handle to the window to receive messages from our tray icon
uID = for our example, we have only 1 icon in the tray so set to zero. If you had more than one icon in the tray, you would give each icon a unique number to check which one was clicked.
uFlags = the fields we are filling in - NIF_ICON, NIF_MESSAGE and NIF_TIP
uCallbackMessage = User defined message that the shell will send to our app when a user clicks our icon in the tray
hIcon = handle to the icon to be displayed in the tray
szTip = pointer to a zero termiated string to be dispalyed as the tooltip when user hovers over our tray icon. Max 64 characters including the NULL. (version 5 of shell32.dll max lenght is 128 characters)
Once the NOTIFYICONDATA structure is filled, we call Shell_NotifyIcon with the message NIM_ADD and the address of the NOTIFYICONDATA structure.
That's it! We now have a Dream In Code icon in the tray area! Eh, what good is it if it does nothing? Let's handle the user interaction (mouse right click and menu display/selection).
WM_SHELLNOTIFY
This is the message we told windows to send to use when something happens to our icon, we are interested when the the user right clicks our icon then we will display a popup menu.
.elseif eax == WM_SHELLNOTIFY
.if wParam == 0
.if lParam == WM_RBUTTONDOWN or WM_RBUTTONUP
invoke GetCursorPos, ADDR pt
invoke SetForegroundWindow, hWin
invoke TrackPopupMenuEx, hPopupMenu, TPM_LEFTALIGN or TPM_LEFTBUTTON, pt.x, pt.y, hWin, 0
invoke PostMessage, hWin, WM_NULL, 0, 0
.endif
.endif
We get the position of the cursor and display our popup menu to the left of where the cursor is. We want to respond to a left mouse button click, so we pass TPM_LEFTBUTTON to the function TrackPopupMenu. Now when
user right clicks our tray icon, a popup menu will be displayed and will only respond to left mouse clicks.
Caveat
There are 2 "bugs" that occur with a popup menu with a tray icon.
1. The menu will not dissapear if you click "outside" of the menu. We fix this by making our "window" the foreground window with SetForegroundWindow right before we display the popup menu BUT this causes bug number 2 to surface.
2. After calling SetForegroundWindow - first time popup menu is displayed, everything is fine, everytime after that the popup menu will display and close right away. This is an "intentional feature" according to Microsoft.
To fix this, we need to "task switch" to our program by posting a dummy message to our window, we use PostMessage for this with a WM_NULL message.
WM_COMMAND
This is where we will respond to the menu selection.
.elseif eax==WM_COMMAND
mov edx,wParam
movzx eax,dx
shr edx,16
.if edx==BN_CLICKED
.if eax == IDM_DIC_NEW
invoke ShellExecute, NULL, offset szOpen, offset szDiCNewTopics, NULL, NULL, SW_SHOWNORMAL or SW_RESTORE
.if dwTimerRunning == TRUE
mov ax, BN_CLICKED
ror eax, 16
mov ax, IDM_STOP
invoke SendMessage, hWin, WM_COMMAND, eax, NULL
push hDiC
pop nid.hIcon
invoke Shell_NotifyIcon, NIM_MODIFY, offset nid
.endif
.elseif eax == IDM_START
invoke ModifyMenu, hPopupMenu, 1, MF_BYPOSITION, IDM_STOP, offset szStop
invoke SetTimer, hWin, ID_TIMER, 500, NULL
mov dwTimerRunning, TRUE
.elseif eax == IDM_STOP
invoke ModifyMenu, hPopupMenu, 1, MF_BYPOSITION, IDM_START, offset szStart
invoke KillTimer, hWin, ID_TIMER
mov dwTimerRunning, FALSE
.elseif eax==IDM_EXIT
invoke Shell_NotifyIcon, NIM_DELETE, offset nid
invoke SendMessage, hWin, WM_CLOSE, 0, 0
.endif
.endif
IDM_DIC_NEW
We get this when user selects our menu - DiC New Topics. We call ShellExecute to open/bring to front the users default webbrowser and display new topics.
We check to see if our timer is active, and if it is we "click" the Stop Timer menu through code with SendMessage and change the tray icon to the DiC icon.
we chage the handle of the icon pointed to in nid.hIcon to our DiC icon and tell the Shell to modify the icon with Shell_NotifyIcon and the message NIM_NOTIFY.
IDM_START
We receive this when user selects the Start Traffic Light menu.
First thing we do is change the menu text from Start Traffic Lights to Stop Traffic Lights and change the menu ID to IDM_STOP, we do this with ModifyMenu
Next we activate our timer to "fire" every half second (we use the timer to change the icons), then we set our flag that our timer is running.
IDM_STOP
We receive this when user select the Stop Traffic Light menu.
We change the menu text from Stop Traffic Light back to Start Traffic Light, and change the menu ID back to IDM_START
Stop the timer and our timer is running flag to false
IDM_EXIT
We receive this when user select exit from menu
First we tell the shell to remove our icon from the tray notification area, then close our app.
WM_TIMER
We receive this message everytime our timer fires.
.if dwCurrentIcon == ICON_RED
push hGreen
pop nid.hIcon
mov dwCurrentIcon, ICON_GREEN
.elseif dwCurrentIcon == ICON_YELLOW
push hRed
pop nid.hIcon
mov dwCurrentIcon, ICON_RED
.else
push hYellow
pop nid.hIcon
mov dwCurrentIcon, ICON_YELLOW
.endif
invoke Shell_NotifyIcon, NIM_MODIFY, offset nid
Here we check to see which icon is being displayed and change the tray icon accordingly
WM_CLOSE
We receive this when user selects the Exit menu
.elseif eax==WM_CLOSE
invoke DestroyIcon, hRed
invoke DestroyIcon, hYellow
invoke DestroyIcon, hGreen
invoke DestroyIcon, hDiC
invoke DestroyMenu, hPopupMenu
invoke KillTimer, hWin, ID_TIMER
invoke DestroyWindow, hWin
Here we are being good programmers and destroying everything we created.
Thats it! Modify and experiment - have fun!!!
App, icons and source attatched.
1CreateMutex
2Kernel Object Namespace
3Message Only Window
Attached File(s)
-
Tray Icon.zip (10.46K)
Number of downloads: 145
This post has been edited by GunnerInc: 25 December 2011 - 07:12 PM







MultiQuote


|