Friday, February 15, 2008 10:06 AM
Refreshing the Taskbar Notification Area
I am working on an automation system that involves forcefully terminating a process that creates an icon in the Taskbar Notification Area (no, not the "system tray"). It is the responsibility of the process that creates an icon in the Taskbar Notification Area to remove the icon when the process exits, however, since I am using TerminateProcess(...) to remotely kill the process, the code to remove the icon never gets executed. As such, the icon remains in the Taskbar Notification Area until one moves the mouse cursor over the icon, at which point it disappears.
Since this is an automation system that's being developed, this icon-creating process will get executed many times, and if left unchecked would end up leaving hundreds of icons in the Taskbar Notification Area (one icon per execution). That's bad.
Despite my best Googling efforts ("refresh notification area", "redraw system tray", etc.), I wasn't able to find elegant code to solve this problem. I found some novel solutions, though. The most common suggestion was to use SetCursor(...) to drag the mouse cursor around the Taskbar Notification Area; while this works, it's an ugly hack and is actually quite slow. One of my "favorite" suggestions was to try to associate each icon in the Taskbar Notification Area with a process, then monitoring each process for termination, then deleting the icon once the given process terminates (talk about overkill... geeze).
When a user moves the mouse over a "dead icon" in the Taskbar Notification Area, some window message must get sent to the window to cause it to say to itself, "hey, the mouse is over me, so let me see if the process that created this icon is still alive.... Oh, it's not? Let me remove the icon, then." I wanted to find what window message was causing that code to fire so that I could send that message to the window myself.
I started up Microsoft Spy++ and saw the following information for the Taskbar Notification Area and its parent windows:
A useful feature of Microsoft Spy++ is that it allows you to monitor window messages sent to a given window. I started monitoring the window messages getting sent to the "Notification Area" window without moving my mouse over the window and saw the following messages getting sent:
The messages above clearly had nothing to do with me moving my mouse (since I wasn't moving my mouse over the window), so I configured Microsoft Spy++ to filter out those messages. Then I moved my mouse over the "dead icon" in question and saw the following messages:
<00001> 00010056 S WM_NCHITTEST xPos:1491 yPos:1024
<00002> 00010056 R WM_NCHITTEST nHittest:HTCLIENT
<00003> 00010056 S WM_SETCURSOR hwnd:00010056 nHittest:HTCLIENT wMouseMsg:WM_MOUSEMOVE
<00004> 00010056 R WM_SETCURSOR fHaltProcessing:False
<00005> 00010056 P WM_MOUSEMOVE fwKeys:0000 xPos:5 yPos:0
<00006> 00010056 S TB_HITTEST pptHitTest:022BFC18
<00007> 00010056 R TB_HITTEST iIndex:0
<00008> 00010056 S TB_DELETEBUTTON iButton:0
<00009> 00010056 R TB_DELETEBUTTON fSucceeded:True
Aha! So either WM_NCHITTEST, WM_SETCURSOR, WM_MOUSEMOVE, or TB_HITTEST leads to the TB_DELETEBUTTON getting sent. After trying to send each window message manually with SendMessage(...), I found which window message was the catalyst: WM_MOUSEMOVE.
With this new-found knowledge, I was able to whip up the following code to refresh the Taskbar Notification Area:
|#define FW(x,y) FindWindowEx(x, NULL, y, L"")|
hNotificationArea = FindWindowEx(
FW(FW(FW(NULL, L"Shell_TrayWnd"), L"TrayNotifyWnd"), L"SysPager"),
for (LONG x = 0; x < r.right; x += 5)
for (LONG y = 0; y < r.bottom; y += 5)
(y << 16) + x);