防止WebBrowser控件窃取焦点?

时间:2022-11-19 09:32:05

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

有没有办法阻止WebBrowser控件导致其父窗体将自己带到前面?

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?

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

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.

当触发WebBrowser的父Form的Deactive事件时,我从其容器中删除WebBrowser,并在再次激活其旧的父窗体时重新添加它。

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

这有点hacky,但它确实有效。不过,我愿意接受任何更好的建议。

2 个解决方案

#1


8  

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.

让我们概括一下这个问题:一个你无法控制的控件或组件,可以调用FlashWindow(Win32 API函数)来引起用户的注意。你不希望这样。

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.

通常有两种解决方案:使用API​​挂钩或消息挂钩。由于API挂钩很复杂且涉及到,我将为Message挂钩提供解决方案。

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:

微软没有用很多词来解释FlashWindow的功能。不幸的是,它不会发送特定的消息(比如WM_FLASH或类似的消息),这会使捕获和废除此行为变得更容易。相反,FlashWindow做了三件事:

  1. It sets a system timer for the flashing intervals
  2. 它为闪烁间隔设置系统计时器

  3. It sends a WM_NCACTIVATE message for the first flash
  4. 它为第一个闪存发送WM_NCACTIVATE消息

  5. It sends a WM_NCACTIVATE message when the timer expires (on receiving WM_SYSTIMER)
  6. 它在计时器到期时发送WM_NCACTIVATE消息(在接收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.

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

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. WM_SYSTIMER事件与PostMessage异步发送,并且不会被表单的WndProc方法接收(它只处理同步消息)

  3. 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
  4. 当用户点击标题栏或任务栏按钮设置输入焦点时,也会使用WM_NCACTIVATE消息,只需取消这些消息就会产生不必要的副作用

  5. FlashWindow will always flash at least once, regardless of the WM_SYSTIMER firing or not.
  6. 无论WM_SYSTIMER是否触发,FlashWindow将始终闪烁至少一次。

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.

WM_SYSTIMER消息未记录。它的值为0x0118,并由Windows内部使用,以便对插入符号的闪烁,菜单打开的延迟等进行计时。这里用于闪烁之间的时间。

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.

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

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.

以下代码为您提供更多控制。您可以使用它来响应闪烁本身。通常,主窗口不经常接收WM_SYSTIMER事件,但您必须尝试是否应该例外。似乎对于FlashWindow,wParam总是设置为0xFFF8,但是要对它进行实验,因为这在任何地方都没有记录。

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:

要激活此messagefilter,只需在表单加载事件中的某处添加以下行:

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.

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

#2


5  

Implement IProtectFocus::AllowFocusChange in an object

在对象中实现IProtectFocus :: AllowFocusChange

Implement IServiceProvider on the same object that implements IOleClientSite, then respond to a IServiceProvider::QueryService for SID_SProtectFocus with an object that exposes IProtectFocus

在实现IOleClientSite的同一对象上实现IServiceProvider,然后使用暴露IProtectFocus的对象响应Ser_SProtectFocus的IServiceProvider :: QueryService

This is a new interface in IE7, so old versions are out of luck.

这是IE7中的一个新界面,因此旧版本运气不佳。

#1


8  

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.

让我们概括一下这个问题:一个你无法控制的控件或组件,可以调用FlashWindow(Win32 API函数)来引起用户的注意。你不希望这样。

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.

通常有两种解决方案:使用API​​挂钩或消息挂钩。由于API挂钩很复杂且涉及到,我将为Message挂钩提供解决方案。

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:

微软没有用很多词来解释FlashWindow的功能。不幸的是,它不会发送特定的消息(比如WM_FLASH或类似的消息),这会使捕获和废除此行为变得更容易。相反,FlashWindow做了三件事:

  1. It sets a system timer for the flashing intervals
  2. 它为闪烁间隔设置系统计时器

  3. It sends a WM_NCACTIVATE message for the first flash
  4. 它为第一个闪存发送WM_NCACTIVATE消息

  5. It sends a WM_NCACTIVATE message when the timer expires (on receiving WM_SYSTIMER)
  6. 它在计时器到期时发送WM_NCACTIVATE消息(在接收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.

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

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. WM_SYSTIMER事件与PostMessage异步发送,并且不会被表单的WndProc方法接收(它只处理同步消息)

  3. 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
  4. 当用户点击标题栏或任务栏按钮设置输入焦点时,也会使用WM_NCACTIVATE消息,只需取消这些消息就会产生不必要的副作用

  5. FlashWindow will always flash at least once, regardless of the WM_SYSTIMER firing or not.
  6. 无论WM_SYSTIMER是否触发,FlashWindow将始终闪烁至少一次。

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.

WM_SYSTIMER消息未记录。它的值为0x0118,并由Windows内部使用,以便对插入符号的闪烁,菜单打开的延迟等进行计时。这里用于闪烁之间的时间。

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.

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

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.

以下代码为您提供更多控制。您可以使用它来响应闪烁本身。通常,主窗口不经常接收WM_SYSTIMER事件,但您必须尝试是否应该例外。似乎对于FlashWindow,wParam总是设置为0xFFF8,但是要对它进行实验,因为这在任何地方都没有记录。

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:

要激活此messagefilter,只需在表单加载事件中的某处添加以下行:

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.

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

#2


5  

Implement IProtectFocus::AllowFocusChange in an object

在对象中实现IProtectFocus :: AllowFocusChange

Implement IServiceProvider on the same object that implements IOleClientSite, then respond to a IServiceProvider::QueryService for SID_SProtectFocus with an object that exposes IProtectFocus

在实现IOleClientSite的同一对象上实现IServiceProvider,然后使用暴露IProtectFocus的对象响应Ser_SProtectFocus的IServiceProvider :: QueryService

This is a new interface in IE7, so old versions are out of luck.

这是IE7中的一个新界面,因此旧版本运气不佳。