There is no (apparent) API for managing icons in the system tray in Windows. Applications that live in the system tray that are aborted through Task Manager, or end without a proper shutdown (error out) will leave their icons in the system tray. Restarting the application then puts another icon in the tray. The zombie icons persist and the only official way to remove them is to float your mouse over them and POOF - they disappear. There simply is no Win32 API for clearing these out. In my research I have found that a lot of folks are looking for the ability to clear these out. I even found one commercial product from a company called APPSVOID But it's bundled in an expensive subscription package.
I suspect the commercial product uses hacks that can be found in various places in languages other than VB. One such can be found at Refresh Notification Area This works, but is written entirely in C++. I'm not a C++ Programmer so I spent some time clumsily refactoring the C++ to vb.net (Framework 4.8 in a Winforms application) and came up with the following and would love it if someone smarter than me could look over my shoulder, laugh a little, and help out:
<pre>Imports System.Runtime.InteropServices
Public Class ClearSystemTray
Private Const WM_MOUSEMOVE As UInteger = &H200
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Private Shared Function PostMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean
End Function
<DllImport("user32.dll", EntryPoint:="FindWindowW")>
Public Shared Function FindWindowW(<MarshalAs(UnmanagedType.LPTStr)> ByVal lpClassName As String, <MarshalAs(UnmanagedType.LPTStr)> ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, ByVal childAfter As IntPtr, ByVal lclassName As String, ByVal windowTitle As String) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Private Shared Function GetClientRect(ByVal hWnd As System.IntPtr, ByRef lpRECT As Rectangle) As Integer
End Function
Public Shared Async Function ClearTray() As Task(Of Task)
Dim SystemTrayContainerHandle As IntPtr = FindWindowW("Shell_TrayWnd", Nothing)
Dim systemTrayHandle As IntPtr = FindWindowEx(SystemTrayContainerHandle, IntPtr.Zero, "TrayNotifyWnd", Nothing)
Dim sysPagerHandle As IntPtr = FindWindowEx(systemTrayHandle, IntPtr.Zero, "SysPager", Nothing)
Dim NotificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "Notification Area")
If NotificationAreaHandle = IntPtr.Zero Then
NotificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "User Promoted Notification Area")
Dim notifyIconOverflowWindowHandle As IntPtr = FindWindowW("NotifyIconOverflowWindow", Nothing)
Dim overflowNotificationAreaHandle As IntPtr = FindWindowEx(notifyIconOverflowWindowHandle, IntPtr.Zero, "ToolbarWindow32", "Overflow Notification Area")
Await RefreshTrayArea(NotificationAreaHandle)
Await RefreshTrayArea(notifyIconOverflowWindowHandle)
Await RefreshTrayArea(overflowNotificationAreaHandle)
Else
Await RefreshTrayArea(NotificationAreaHandle)
End If
Application.DoEvents()
Return Task.CompletedTask
End Function
Private Shared Async Function RefreshTrayArea(windowHandle As IntPtr) As Task(Of Task)
Dim rect As Rectangle
GetClientRect(windowHandle, rect)
Dim iPos As Integer
Await Task.Run(Sub()
For x As Integer = 0 To rect.Right - 1 Step 5
For y As Integer = 0 To rect.Bottom - 1 Step 5
iPos = (y << 16) + x
PostMessage(windowHandle, WM_MOUSEMOVE, 0, iPos)
Next
Application.DoEvents()
Next
End Sub)
Return Task.CompletedTask
End Function
Since I can barely read the C++ code I have no idea if I did this correctly or not - but... it appears to work.
Here is the original C++ code from the above mentioned site:
<pre>#include "stdafx.h"
#define FW(x,y) FindWindowEx(x, NULL, y, L"")
int _tmain(int argc, _TCHAR* argv[])
{
HWND hNotificationArea;
RECT r;
GetClientRect(
hNotificationArea = FindWindowEx(
FW(FW(FW(NULL, L"Shell_TrayWnd"), L"TrayNotifyWnd"), L"SysPager"),
NULL,
L"ToolbarWindow32",
L"Notification Area"),
&r);
for (LONG x = 0; x < r.right; x += 5)
for (LONG y = 0; y < r.bottom; y += 5)
SendMessage(
hNotificationArea,
WM_MOUSEMOVE,
0,
(y << 16) + x);
GetClientRect(
hNotificationArea = FindWindowEx(
FW(FW(FW(NULL, L"Shell_TrayWnd"), L"TrayNotifyWnd"), L"SysPager"),
NULL,
L"ToolbarWindow32",
L"User Promoted Notification Area"),
&r);
for (LONG x = 0; x < r.right; x += 5)
for (LONG y = 0; y < r.bottom; y += 5)
SendMessage(
hNotificationArea,
WM_MOUSEMOVE,
0,
(y << 16) + x);
GetClientRect(
hNotificationArea = FindWindowEx(
FW(NULL, L"NotifyIconOverflowWindow"),
NULL,
L"ToolbarWindow32",
L"Overflow Notification Area"),
&r);
for (LONG x = 0; x < r.right; x += 5)
for (LONG y = 0; y < r.bottom; y += 5)
SendMessage(
hNotificationArea,
WM_MOUSEMOVE,
0,
(y << 16) + x);
return 0;
}
If someone here is literate in C++ it would be great if you could look this over and see what dumb things I may have done. I am not offended by legitimate criticism (saying you shouldn't be using VB is not legitimate criticism). Also - if someone thinks it's valuable to have this in C# I'm game for that.
modified 29-Nov-22 8:21am.
|