编辑:完整的问题已经重新编写,我误解了原始问题。
让我们将问题归纳为一个控件或组件,你无法对其进行控制,但可以调用FlashWindow
(Win32 API函数)以引起用户的注意。你不想这样。
通常有两种解决方案:使用API挂钩或消息挂钩。由于API挂钩很复杂,所以我将介绍一种消息挂钩的解决方案。
FlashWindow
微软没有明确说明FlashWindow
的作用。不幸的是,它没有发送特定的消息(例如WM_FLASH
或类似消息),否则就会更容易捕获和取消此行为。相反,FlashWindow
执行以下三个操作:
- 设置闪烁间隔的系统计时器
- 在第一次闪烁时发送
WM_NCACTIVATE
消息
- 在计时器到期时(接收到
WM_SYSTIMER
)发送WM_NCACTIVATE
消息
取决于组件如何调用FlashWindow,这可能是无限期的,直到发生另一个超时,直到它获得焦点或仅一次。每个WM_NCACTIVATE消息都会激活或取消激活NC区域(标题栏,任务栏上的按钮)。它不会更改输入焦点。
挑战
任何防止闪烁的解决方案都有些复杂。主要挑战包括:
WM_SYSTIMER
事件是异步发送的PostMessage,并且不会被窗体的WndProc
方法接收到(它只处理同步消息)
WM_NCACTIVATE
消息也用于当用户单击标题栏或任务栏按钮以设置输入焦点时,简单地取消这些消息将产生不良影响
- 无论
WM_SYSTIMER
是否触发,FlashWindow都至少会闪烁一次。
WM_SYSTIMER
消息是未记录的。它的值为0x0118
,在Windows内部用于计时诸如光标闪烁、菜单打开延迟等事项。在此处,它用于闪烁之间的时间。
解决方案
我在此提供的解决方案是进一步开发的基础。它不是完整的解决方案,但可以在许多情况下解决问题。将以下代码放入你的窗体代码中:
protected override void WndProc(ref Message m)
{
bool messageHandled = false;
if (m.Msg == WM_NCACTIVATE)
{
m.Result = IntPtr.Zero;
messageHandled = true;
}
if(!messageHandled)
base.WndProc(ref m);
}
上面的代码已经完全防止了闪烁。您需要添加一些逻辑来更改标题栏,因为完全忽略
WM_NCACTIVATE
意味着标题栏看起来始终处于活动状态,即使它并不是。
以下代码可以给您更多的控制权。您可以使用它来对闪烁本身做出反应。通常,主窗口不会经常收到
WM_SYSTIMER
事件,但您需要进行实验,以确定是否应该进行例外处理。似乎对于
FlashWindow
,
wParam
总是设置为
0xFFF8
,但请进行实验,因为这在任何地方都没有记录。
public class MyMessageFilter : IMessageFilter
{
IntPtr FilteredHwnd = IntPtr.Zero;
public MyMessageFilter(IntPtr hwnd)
{
this.FilteredHwnd = hwnd;
}
public bool PreFilterMessage(ref Message m)
{
if (this.FilteredHwnd == m.HWnd && m.Msg == WM_SYSTIMER)
return true;
else
return false;
}
}
要激活此消息过滤器,只需在表单加载事件中添加以下行:
Application.AddMessageFilter(new MyMessageFilter(this.Handle))
以下常量应放置在类级别。它们在上述两个代码部分中均被使用:
public const UInt32 WM_SYSTIMER = 0x0118;
public const UInt32 WM_NCACTIVATE = 0x86;
结论
虽然这个问题本身是可以解决的,但并不容易。使用上述方法,你应该能够解决大部分问题。使用过滤器来防止闪烁,但第一次“闪烁”仍会发生。使用WinProc
覆盖以防止第一个问题的发生,但需要添加一些逻辑来防止你的应用程序行为过于奇怪(例如:始终处于非活动状态的标题栏或始终处于活动状态)。你已经有了一些代码,可以与此组合使用来设置一些布尔标志。