FlashWindowEx是Windows官方推荐的轻量级窗口提醒方案,通过任务栏闪烁和标题栏高亮实现通知,不抢占焦点、不卡顿、兼容高DPI与多显示器,需P/Invoke声明并正确设置FLASHWINFO结构体字段。

Windows API 的 FlashWindowEx 是最轻量的抖动替代方案
纯 C# 没有“窗口抖动”原生 API,硬靠定时器移动窗体位置不仅卡顿、易被系统拦截(尤其在多显示器或高 DPI 下),还会触发窗口重绘异常。实际项目里没人真去写位移动画——用户要的是“被注意到”,不是物理抖动。
用 FlashWindowEx 是 Windows 官方支持的提醒方式,效果等同于 QQ/微信未读消息时的任务栏闪烁+标题栏高亮,且不抢占焦点、不打断用户当前操作。
- 必须先 P/Invoke 声明:
FlashWindowEx、FLASHWINFO结构体 -
dwFlags至少包含FLASHW_TRAY | FLASHW_TIMERNOFG:确保任务栏图标闪烁,且窗口不在前台时也生效 - 别漏掉
cbSize = Marshal.SizeOf<FLASHWINFO>(),否则调用直接失败,返回false - 调用后无需手动停止,传入的
uCount控制闪烁次数(建议 3–5 次),系统自动清理
C# 窗体中调用 FlashWindowEx 的最小可行代码
不是贴一整段 WinForm 类,而是聚焦“在哪调、怎么传参、哪容易错”。以下代码可直接塞进任意窗体的方法里(比如收到新消息时):
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool FlashWindowEx(ref FLASHWINFO pwfi);
<p>[StructLayout(LayoutKind.Sequential)]
public struct FLASHWINFO
{
public uint cbSize;
public IntPtr hwnd;
public uint dwFlags;
public uint uCount;
public uint dwTimeout;
}</p><p>// 实际触发提醒
var flashInfo = new FLASHWINFO
{
cbSize = (uint)Marshal.SizeOf<FLASHWINFO>(),
hwnd = this.Handle,
dwFlags = 0x00000004 | 0x00000008, // FLASHW_TRAY | FLASHW_TIMERNOFG
uCount = 4,
dwTimeout = 0
};
FlashWindowEx(ref flashInfo);
注意:dwFlags 用十六进制字面量更安全,避免引用未定义常量;this.Handle 必须是已创建的窗体句柄(不能在构造函数里调,得等 Load 事件之后)。
为什么不用 SetWindowPos + 定时器模拟抖动
网上有些示例用 SetWindowPos 反复偏移窗口坐标,看似“抖”了,但问题一堆:
- 高 DPI 缩放下坐标计算错乱,抖动方向飘忽、幅度失真
- 多显示器场景中,窗口可能被甩到不可见区域,甚至卡死在屏幕边缘
- 触发系统“窗口移动动画”,和 Aero 或 Fluent 动效冲突,导致界面撕裂或假死
- 如果用户正拖拽窗口,你的定时器会强行中断操作,体验极差
- 部分安全软件/桌面环境(如某些国产管家)会拦截非常规窗口位移,直接静默失败
这些不是边界情况——是 Windows 桌面交互的正常约束。想“抖”,就老实用系统级提醒机制。
WPF 或 .NET 6+ 应用的适配要点
WPF 窗体没有 Handle,需先获取 HWND:
- 用
new WindowInteropHelper(this).Handle(this是Window实例) - .NET 6+ WinForms 中若启用
HighDpiMode,Handle依然有效,但别在OnHandleCreated之前调用FlashWindowEx - 如果窗体设置了
ShowInTaskbar = false,FlashWindowEx无效——这是设计使然,没任务栏图标就无法闪烁 - UWP/WinUI 不支持该 API,得换 Toast Notification 或系统通知中心集成
真正容易被忽略的,是窗体生命周期:Handle 可能为 IntPtr.Zero,尤其是在异步回调里触发提醒时。加一句 if (this.IsHandleCreated) 判断比捕获异常更干净。










