Remove pesky "zombie" icons from the Windows System Tray
-
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:
Imports System.Runtime.InteropServices
Public Class ClearSystemTray
Private Const WM\_MOUSEMOVE As UInteger = &H200 Private Shared Function PostMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean End Function ' 'Private Shared Function SendMessageW(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As Integer 'End Function Public Shared Function FindWindowW( ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr End Function Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, ByVal childAfter As IntPtr, ByVal lclassName As String, ByVal windowTitle As String) As IntPtr End Function Private Shared Function GetClientRect(ByVal hWnd As System.IntPtr,
-
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:
Imports System.Runtime.InteropServices
Public Class ClearSystemTray
Private Const WM\_MOUSEMOVE As UInteger = &H200 Private Shared Function PostMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean End Function ' 'Private Shared Function SendMessageW(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As Integer 'End Function Public Shared Function FindWindowW( ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr End Function Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, ByVal childAfter As IntPtr, ByVal lclassName As String, ByVal windowTitle As String) As IntPtr End Function Private Shared Function GetClientRect(ByVal hWnd As System.IntPtr,
The obvious "dumb thing" was linking to the expensive commercial application, which is technically spam. :) Also, returning
Task(Of Task)
is unnecessary; just have the function returnTask
, and remove theReturn Task.CompletedTask
lines. And callingApplication.DoEvents
is a code-smell. Aside from the fact that it ties your code to WinForms, it will also lead to unexpected behaviour.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
The obvious "dumb thing" was linking to the expensive commercial application, which is technically spam. :) Also, returning
Task(Of Task)
is unnecessary; just have the function returnTask
, and remove theReturn Task.CompletedTask
lines. And callingApplication.DoEvents
is a code-smell. Aside from the fact that it ties your code to WinForms, it will also lead to unexpected behaviour.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
Thanks - yes - you are right. I will see if I can edit the link away. If people are interested they can google it themselves! The Application.DoEvents are not in my final version - I should have removed them before I posted. I often use those to host a breakpoint for convenient place to examine values. Yes - I do occasionally leave them accidentally so I would agree, that remains in the dumb category. However, I do find the occasional DoEvents necessary in some synchronously running tight loops. I thought an async function should always return a value and some examples from which I learned a long time ago have that task(of task) with the return of task.completedtask. Wow - that is all over my code...
-
Thanks - yes - you are right. I will see if I can edit the link away. If people are interested they can google it themselves! The Application.DoEvents are not in my final version - I should have removed them before I posted. I often use those to host a breakpoint for convenient place to examine values. Yes - I do occasionally leave them accidentally so I would agree, that remains in the dumb category. However, I do find the occasional DoEvents necessary in some synchronously running tight loops. I thought an async function should always return a value and some examples from which I learned a long time ago have that task(of task) with the return of task.completedtask. Wow - that is all over my code...
Dan Desjardins wrote:
I thought an async function should always return a value
Perhaps you were confused by the valid "avoid async void" warning - in VB.NET, that would be the equivalent of an
Async Sub
: Avoid async void methods | You’ve Been Haacked[^] AnAsync Function
that returns a non-genericTask
is similar to a synchronousSub
that doesn't return anything. AnAsync Function
that returnsTask(Of Foo)
is similar to a synchronousFunction
that returnsFoo
. In other words:Sub PrintSync()
...
End SubPrintSync()
would become:
Async Function PrintAsync() As Task
...
End FunctionAwait PrintAsync()
And:
Function CalculateSync() As Integer
...
Return 42
End FunctionDim i As Integer = CalculateSync()
would become:
Async Function CalculateAsync() As Task(Of Integer)
...
Return 42
End FunctionDim i As Integer = Await CalculateAsync()
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
Dan Desjardins wrote:
I thought an async function should always return a value
Perhaps you were confused by the valid "avoid async void" warning - in VB.NET, that would be the equivalent of an
Async Sub
: Avoid async void methods | You’ve Been Haacked[^] AnAsync Function
that returns a non-genericTask
is similar to a synchronousSub
that doesn't return anything. AnAsync Function
that returnsTask(Of Foo)
is similar to a synchronousFunction
that returnsFoo
. In other words:Sub PrintSync()
...
End SubPrintSync()
would become:
Async Function PrintAsync() As Task
...
End FunctionAwait PrintAsync()
And:
Function CalculateSync() As Integer
...
Return 42
End FunctionDim i As Integer = CalculateSync()
would become:
Async Function CalculateAsync() As Task(Of Integer)
...
Return 42
End FunctionDim i As Integer = Await CalculateAsync()
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
And now that makes sense to me - especially since your return int is 42 :cool: