防止 WebBrowser 控件窃取焦点? [英] Prevent WebBrowser control from stealing focus?

查看:29
本文介绍了防止 WebBrowser 控件窃取焦点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有办法阻止 WebBrowser 控件导致其父窗体出现在前面?

如果使用 InvokeScript 方法在主父文档中的 iframe 上调用调用 focus() 的 JavaScript 函数,将导致窗口直接将自身置于最前面(或至少导致任务栏图标开始闪烁)).有没有办法防止这种情况发生?

更新:

我找到了解决问题的临时方法.

当 WebBrowser 的父表单的 Deactive 事件被触发时,我从它的容器中删除了 WebBrowser,并在其旧的父表单再次被激活时重新添加它.

这有点笨拙,但它有效.不过,我愿意接受任何更好的建议.

解决方案

完整的问题重写,我误解了原始问题

让我们概括一下问题:您无法控制的控件或组件可以调用FlashWindow(Win32 API 函数)来引起用户的注意.你不想那样.

对此一般有两种解决方案:使用API​​ hooking或Message hooking.由于 API hooking 复杂且涉及,我将介绍 Message hooking 的解决方案.

FlashWindow

微软并没有用太多的话来解释 FlashWindow 的作用.不幸的是,它不会发送特定的消息(比如 WM_FLASH 或类似的),这会使捕获和取消这种行为变得更容易.相反,FlashWindow 做了三件事:

  1. 它为闪烁间隔设置系统计时器
  2. 它为第一次闪光发送一个WM_NCACTIVATE消息
  3. 它在定时器到期时发送一个 WM_NCACTIVATE 消息(在接收到 WM_SYSTIMER 时)

根据组件调用 FlashWindow 的方式,这可以是不确定的,直到发生另一个超时,直到它获得焦点或只有一次.每个 WM_NCACTIVATE 消息都会激活或停用 NC 区域(标题栏、任务栏上的按钮).它不会改变输入的焦点.

挑战

任何防止闪烁的解决方案都有点涉及.主要挑战是:

  1. WM_SYSTIMER 事件与 PostMessage 异步发送,不被 Form 的 WndProc 方法接收(它只处理同步消息)
  2. WM_NCACTIVATE 消息也会在用户点击标题栏或任务栏按钮设置输入焦点时使用,简单地取消这些消息会产生不必要的副作用
  3. FlashWindow 将始终至少闪烁一次,无论 WM_SYSTIMER 是否触发.

WM_SYSTIMER 消息未记录.它的值为 0x0118,Windows 内部使用它来计时插入符号的闪烁、菜单打开的延迟等.这里它用于闪烁之间的时间.>

解决方案

我在这里提出的解决方案是进一步开发的基础.它不是一个完整的解决方案,但它解决了许多情况下的问题.将以下内容放入您的表单代码中:

protected override void WndProc(ref Message m){bool messageHandled = false;if (m.Msg == WM_NCACTIVATE){//在此处添加逻辑以确定用户操作、失去焦点等并设置//messageHandled 和 m.Result 仅当用户操作不是原因时//触发 WM_NCACTIVATEm.Result = IntPtr.Zero;消息处理 = 真;}如果(!消息处理)base.WndProc(ref m);}

上面的代码已经完全防止了闪烁.您必须添加一些逻辑来更改标题栏,因为完全忽略 WM_NCACTIVATE 意味着标题栏将始终看起来处于活动状态,即使它不是.

以下代码为您提供了更多控制权.您可以使用它对闪烁本身做出反应.通常,主窗口不会如此频繁地接收 WM_SYSTIMER 事件,但您必须尝试是否应该例外.似乎对于 FlashWindowwParam 总是设置为 0xFFF8,但请尝试使用它,因为这在任何地方都没有记录.>

公共类 MyMessageFilter : IMessageFilter{//一个应用程序可以有多个窗口,一次只过滤一个窗口IntPtr FilteredHwnd = IntPtr.Zero;公共 MyMessageFilter(IntPtr hwnd){this.FilteredHwnd = hwnd;}public bool PreFilterMessage(ref Message m){if (this.FilteredHwnd == m.HWnd && m.Msg == WM_SYSTIMER)返回真;//停止进一步处理消息别的返回假;//所有其他消息:处理它们}}

要激活此消息过滤器,只需在表单加载事件中的某处添加以下行:

Application.AddMessageFilter(new MyMessageFilter(this.Handle));

以下常量应该放在类级别.它们用于上面的两个代码部分:

public const UInt32 WM_SYSTIMER = 0x0118;公共常量 UInt32 WM_NCACTIVATE = 0x86;

结论

虽然问题本身是可以解决的,但到目前为止并不容易.有了上面的句柄,你应该走得很远.使用过滤器来防止闪烁,但第一次闪烁"仍然发生.使用 WinProc 覆盖也可以防止第一个,但添加一些逻辑以防止您的应用程序表现得太奇怪(即:始终处于非活动状态的标题栏,或始终处于活动状态).您已经有了一些代码,可以与它结合起来设置一些布尔标志.

Is there a way to stop the WebBrowser control from causing its parent form to bring itself to the front?

If you use the InvokeScript method to invoke a JavaScript function that calls focus() on an iframe within the main parent document, it will cause the window to bring itself directly to the front(or atleast cause the taskbar icon to start flashing). Is there a way to prevent this from happening?

Update:

I've found a temporary answer to my problem.

When the WebBrowser's parent Form's Deactive event is fired, I remove the WebBrowser from its container, and re-add it when its old parent form is activated again.

It's kind of hacky, but it works. I'm open to any better suggestions, though.

解决方案

EDIT: complete question rewritten, I misunderstood original question

Let's generalize the problem: a control or component that you don't have control about, can call FlashWindow (the Win32 API function) to get attention from the user. You don't want that.

There are generally two solutions for this: use API hooking or Message hooking. Since API hooking is complex and involved, I'll present a solution for Message hooking.

FlashWindow

Microsoft doesn't explain in so many words what FlashWindow does. Unfortunately, it doesn't send a specific message (say WM_FLASH or similar), which would've made it easier to capture and annul this behavior. Instead, FlashWindow does three things:

  1. It sets a system timer for the flashing intervals
  2. It sends a WM_NCACTIVATE message for the first flash
  3. It sends a WM_NCACTIVATE message when the timer expires (on receiving WM_SYSTIMER)

Depending on how the component calls FlashWindow, this can be indefinite, until another timeout occurs, until it has focus or just once. Each WM_NCACTIVATE message activates or deactivates the NC-zone (caption bar, button on taskbar). It doesn't change the input the focus.

Challenge

Any solution for preventing the flashing is a bit involved. The main challenges are:

  1. the WM_SYSTIMER event is sent asynchronously with PostMessage and is not received by the WndProc method of the Form (it only processes synchronous messages)
  2. the WM_NCACTIVATE messages are also used when the user clicks on the title bar or taskbar button to set input focus, simply canceling these messages will have unwanted side effects
  3. FlashWindow will always flash at least once, regardless of the WM_SYSTIMER firing or not.

The WM_SYSTIMER message is undocumented. It has the value 0x0118 and is used internally by Windows to time such things as the blinking of the caret, the delay in a menu opening etc. Here it is used for the time between the flashes.

Solution

The solution I present here is a basis for further development. It is not a complete solution, but it solves the issue in many cases. Place the following in your form code:

protected override void WndProc(ref Message m)
{
    bool messageHandled = false;
    if (m.Msg == WM_NCACTIVATE)
    {
        // add logic here to determine user action, losing focus etc and set 
        // messageHandled and m.Result only when user action is not the cause 
        // of triggering WM_NCACTIVATE
        m.Result = IntPtr.Zero;
        messageHandled = true;
    }

    if(!messageHandled)
        base.WndProc(ref m);
}

The above code already prevents flashing completely. You'll have to add some logic to change the title bar, because totally ignoring WM_NCACTIVATE means that the title bar will look active all the time, even when it isn't.

The following code gives you more control. You can use it to react to the flashing itself. Normally, a main window does not receive WM_SYSTIMER events so often, but you'll have to experiment whether you should make exceptions. It seems that for FlashWindow, the wParam is always set to 0xFFF8, but do experiment with it, as this is not documented anywhere.

public class MyMessageFilter : IMessageFilter
{
    // an application can have many windows, only filter for one window at the time
    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;     // stop handling the message further
        else
            return false;    // all other msgs: handle them
    }
}

To activate this messagefilter, simply add the following line somewhere in your form load event:

Application.AddMessageFilter(new MyMessageFilter(this.Handle));

The following constants should be placed at class level. They are used in both code sections above:

public const UInt32 WM_SYSTIMER = 0x0118;
public const UInt32 WM_NCACTIVATE = 0x86;

Conclusion

Though the problem itself is solvable, it is by far not easy. With the above handles, you should get quite far. Use the filter to prevent the flashing, but then the first "flash" still happens. Use the WinProc override to prevent the first one too, but add some logic to prevent your application from behaving too oddly (i.e.: always inactive title bar, or always active). You already have some code that you can combine with this to set some boolean flags.

这篇关于防止 WebBrowser 控件窃取焦点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆