使用VB.NET清除系统托盘“僵尸”图标(C++代码改写版)
Windows系统托盘僵尸图标清除代码检查与修正
Windows没有公开的系统托盘图标管理API,当托盘应用被任务管理器强制终止或异常退出时,会留下“僵尸”图标,重启应用还会新增图标,官方仅有的清除方式是鼠标悬停让其消失。我将一段C++实现的托盘刷新代码改写为VB.NET(.NET Framework 4.8 WinForms)版本,代码能运行但不确定逻辑是否正确,现寻求验证并提供C#版本。
原C++代码逻辑分析
原C++代码会依次处理三个系统托盘相关区域,通过发送WM_MOUSEMOVE消息模拟鼠标悬停,触发系统刷新图标:
Notification Area:对应WinXP及早期系统的托盘区域User Promoted Notification Area:当前系统的可见托盘图标区域Overflow Notification Area:托盘隐藏图标(点击向上箭头展开的区域)
VB.NET代码问题点
对比原C++代码,改写的VB.NET存在以下逻辑偏差:
- 区域处理逻辑错误:原代码是三个区域全部处理,VB.NET代码仅在第一个区域找不到时才处理后两个,遗漏了正常情况下的可见图标和隐藏图标区域刷新。
- 错误传递窗口句柄:
RefreshTrayArea传入了notifyIconOverflowWindowHandle(父窗口),但原代码是传入其内部的Overflow Notification Area控件句柄。 - 返回值冗余:
ClearTray和RefreshTrayArea返回Task(Of Task),实际只需返回Task即可。 - 消息发送方式差异:原代码用
SendMessage(同步发送),VB.NET用PostMessage(异步发送),虽不影响功能,但同步方式更贴合原逻辑。
修正后的VB.NET代码
Imports System.Runtime.InteropServices Imports System.Drawing Public Class ClearSystemTray Private Const WM_MOUSEMOVE As UInteger = &H200 <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr End Function <DllImport("user32.dll", EntryPoint:="FindWindowW")> Private 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 IntPtr, ByRef lpRECT As Rectangle) As Boolean End Function Public Shared Async Function ClearTray() As Task ' 获取托盘容器句柄 Dim shellTrayWnd = FindWindowW("Shell_TrayWnd", Nothing) If shellTrayWnd = IntPtr.Zero Then Return Dim trayNotifyWnd = FindWindowEx(shellTrayWnd, IntPtr.Zero, "TrayNotifyWnd", Nothing) If trayNotifyWnd = IntPtr.Zero Then Return Dim sysPager = FindWindowEx(trayNotifyWnd, IntPtr.Zero, "SysPager", Nothing) If sysPager = IntPtr.Zero Then Return ' 处理WinXP兼容区域 Dim notificationArea = FindWindowEx(sysPager, IntPtr.Zero, "ToolbarWindow32", "Notification Area") If notificationArea <> IntPtr.Zero Then Await RefreshTrayArea(notificationArea) End If ' 处理可见图标区域 Dim userPromotedArea = FindWindowEx(sysPager, IntPtr.Zero, "ToolbarWindow32", "User Promoted Notification Area") If userPromotedArea <> IntPtr.Zero Then Await RefreshTrayArea(userPromotedArea) End If ' 处理隐藏图标溢出区域 Dim overflowWindow = FindWindowW("NotifyIconOverflowWindow", Nothing) If overflowWindow <> IntPtr.Zero Then Dim overflowArea = FindWindowEx(overflowWindow, IntPtr.Zero, "ToolbarWindow32", "Overflow Notification Area") If overflowArea <> IntPtr.Zero Then Await RefreshTrayArea(overflowArea) End If End If End Function Private Shared Async Function RefreshTrayArea(windowHandle As IntPtr) As Task If windowHandle = IntPtr.Zero Then Return Dim rect As Rectangle If Not GetClientRect(windowHandle, rect) Then Return Await Task.Run(Sub() For x As Integer = 0 To rect.Right Step 5 For y As Integer = 0 To rect.Bottom Step 5 ' 构造鼠标位置参数:高16位是Y坐标,低16位是X坐标 Dim lParam = CType((y << 16) Or (x And &HFFFF), IntPtr) SendMessage(windowHandle, WM_MOUSEMOVE, IntPtr.Zero, lParam) Next Next End Sub) End Function End Class
对应的C#版本
using System; using System.Drawing; using System.Runtime.InteropServices; using System.Threading.Tasks; public static class ClearSystemTray { private const uint WM_MOUSEMOVE = 0x200; [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", EntryPoint = "FindWindowW")] private static extern IntPtr FindWindowW([MarshalAs(UnmanagedType.LPTStr)] string lpClassName, [MarshalAs(UnmanagedType.LPTStr)] string lpWindowName); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern bool GetClientRect(IntPtr hWnd, ref Rectangle lpRECT); public static async Task ClearTray() { var shellTrayWnd = FindWindowW("Shell_TrayWnd", null); if (shellTrayWnd == IntPtr.Zero) return; var trayNotifyWnd = FindWindowEx(shellTrayWnd, IntPtr.Zero, "TrayNotifyWnd", null); if (trayNotifyWnd == IntPtr.Zero) return; var sysPager = FindWindowEx(trayNotifyWnd, IntPtr.Zero, "SysPager", null); if (sysPager == IntPtr.Zero) return; // 处理WinXP兼容区域 var notificationArea = FindWindowEx(sysPager, IntPtr.Zero, "ToolbarWindow32", "Notification Area"); if (notificationArea != IntPtr.Zero) { await RefreshTrayArea(notificationArea); } // 处理可见图标区域 var userPromotedArea = FindWindowEx(sysPager, IntPtr.Zero, "ToolbarWindow32", "User Promoted Notification Area"); if (userPromotedArea != IntPtr.Zero) { await RefreshTrayArea(userPromotedArea); } // 处理隐藏图标溢出区域 var overflowWindow = FindWindowW("NotifyIconOverflowWindow", null); if (overflowWindow != IntPtr.Zero) { var overflowArea = FindWindowEx(overflowWindow, IntPtr.Zero, "ToolbarWindow32", "Overflow Notification Area"); if (overflowArea != IntPtr.Zero) { await RefreshTrayArea(overflowArea); } } } private static async Task RefreshTrayArea(IntPtr windowHandle) { if (windowHandle == IntPtr.Zero) return; var rect = new Rectangle(); if (!GetClientRect(windowHandle, ref rect)) return; await Task.Run(() => { for (int x = 0; x <= rect.Right; x += 5) { for (int y = 0; y <= rect.Bottom; y += 5) { // 构造鼠标位置参数:高16位是Y坐标,低16位是X坐标 var lParam = (IntPtr)((y << 16) | (x & 0xFFFF)); SendMessage(windowHandle, WM_MOUSEMOVE, IntPtr.Zero, lParam); } } }); } }
内容的提问来源于stack exchange,提问作者Destek
相关产品推荐
相关产品推荐

