wpf WebBrowser 控制键盘和焦点行为
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18256886/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WebBrowser control keyboard and focus behavior
提问by noseratio
Apparently, there are some serious keyboard and focus issues with WPF WebBrowser control. I've put together a trivial WPF app, just a WebBrowser and two buttons. The app loads a very basic editable HTML markup (<body contentEditable='true'>some text</body>) and demonstrates the following:
显然,WPF WebBrowser 控件存在一些严重的键盘和焦点问题。我已经组装了一个简单的 WPF 应用程序,只有一个 WebBrowser 和两个按钮。该应用程序加载一个非常基本的可编辑 HTML 标记 ( <body contentEditable='true'>some text</body>) 并演示以下内容:
Tabbing is misbehaving. User needs to hit Tab twice to see the caret (text cursor) inside WebBrowser and be able to type.
When user switches away from the app (e.g., with Alt-Tab), then goes back, the caret is gone and she is unable to type at all. A physical mouse click into the WebBrowser's window client area is required to get back the caret and keystrokes.
Inconsistently, a dotted focus rectangle shows up around WebBrowser (when tabbing, but not when clicking). I could not find a way to get rid of it (
FocusVisualStyle="{x:Null}"does not help).Internally, WebBrowser never receives the focus. That's true for both logical focus (FocusManager) and input focus (Keyboard). The
Keyboard.GotKeyboardFocusEventandFocusManager.GotFocusEventevents never get fired for WebBrowser (although they both do for buttons in the same focus scope). Even when the caret is inside WebBrowser,FocusManager.GetFocusedElement(mainWindow)points to a previously focused element (a button) andKeyboard.FocusedElementisnull. At the same time,((IKeyboardInputSink)this.webBrowser).HasFocusWithin()returnstrue.
标签是行为不端。用户需要点击 Tab 两次才能看到 WebBrowser 中的插入符号(文本光标)并能够输入。
当用户离开应用程序(例如,使用 Alt-Tab),然后返回时,插入符号消失了,她根本无法输入。需要物理鼠标单击 WebBrowser 的窗口客户区才能恢复插入符号和击键。
不一致的是,一个虚线焦点矩形出现在 WebBrowser 周围(当 Tab 键时,而不是在单击时)。我找不到摆脱它的方法(
FocusVisualStyle="{x:Null}"没有帮助)。在内部,WebBrowser 永远不会获得焦点。对于逻辑焦点 ( FocusManager) 和输入焦点 ( Keyboard) 都是如此。将
Keyboard.GotKeyboardFocusEvent和FocusManager.GotFocusEvent从不事件被解雇的web浏览器(虽然他们都对按钮做在同一个焦点范围)。即使插入符号在 WebBrowser 中,也FocusManager.GetFocusedElement(mainWindow)指向先前聚焦的元素(按钮)并且Keyboard.FocusedElement是null。同时,((IKeyboardInputSink)this.webBrowser).HasFocusWithin()返回true。
I'd say, such behaviour is almost too dysfunctional to be true, but that's how it works. I could probably come up with some hacks to fix it and bring it in row with native WPF controls like TextBox. Still I hope, maybe I'm missing something obscure yet simple here. Has anyone dealt with a similar problem? Any suggestions on how to fix this would be greatly appreciated.
我想说,这种行为几乎太不正常了,不可能是真的,但这就是它的工作原理。我可能会想出一些技巧来修复它并将它与本机 WPF 控件(如TextBox. 我仍然希望,也许我在这里遗漏了一些晦涩而简单的东西。有没有人处理过类似的问题?任何有关如何解决此问题的建议将不胜感激。
At this point, I'm inclined to develop an in-house WPF wrapper for WebBrowser ActiveX Control, based upon HwndHost. We are also considering other alternativesto WebBrowser, such as Chromium Embedded Framework (CEF).
在这一点上,我倾向于基于HwndHost为 WebBrowser ActiveX 控件开发内部 WPF 包装器。我们还在考虑WebBrowser 的其他替代方案,例如 Chromium Embedded Framework (CEF)。
The VS2012 project can be downloaded from herein case someone wants to play with it.
VS2012 项目可以从这里下载,以防有人想玩它。
This is XAML:
这是 XAML:
<Window x:Class="WpfWebBrowserTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="640" Height="480" Background="LightGray">
<StackPanel Margin="20,20,20,20">
<ToggleButton Name="btnLoad" Focusable="True" IsTabStop="True" Content="Load" Click="btnLoad_Click" Width="100"/>
<WebBrowser Name="webBrowser" Focusable="True" KeyboardNavigation.IsTabStop="True" FocusVisualStyle="{x:Null}" Height="300"/>
<Button Name="btnClose" Focusable="True" IsTabStop="True" Content="Close" Click="btnClose_Click" Width="100"/>
</StackPanel>
</Window>
This is C# code, it has a bunch of diagnostic traces to show how focus/keyboard events are routed and where the focus is:
这是 C# 代码,它有一堆诊断跟踪来显示焦点/键盘事件是如何路由的以及焦点在哪里:
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;
namespace WpfWebBrowserTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// watch these events for diagnostics
EventManager.RegisterClassHandler(typeof(UIElement), Keyboard.PreviewKeyDownEvent, new KeyEventHandler(MainWindow_PreviewKeyDown));
EventManager.RegisterClassHandler(typeof(UIElement), Keyboard.GotKeyboardFocusEvent, new KeyboardFocusChangedEventHandler(MainWindow_GotKeyboardFocus));
EventManager.RegisterClassHandler(typeof(UIElement), FocusManager.GotFocusEvent, new RoutedEventHandler(MainWindow_GotFocus));
}
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
// load the browser
this.webBrowser.NavigateToString("<body contentEditable='true' onload='focus()'>Line 1<br>Line 3<br>Line 3<br></body>");
this.btnLoad.IsChecked = true;
}
private void btnClose_Click(object sender, RoutedEventArgs e)
{
// close the form
if (MessageBox.Show("Close it?", this.Title, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
this.Close();
}
// Diagnostic events
void MainWindow_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
Debug.Print("{0}, source: {1}, {2}", FormatMethodName(), FormatType(e.Source), FormatFocused());
}
void MainWindow_GotFocus(object sender, RoutedEventArgs e)
{
Debug.Print("{0}, source: {1}, {2}", FormatMethodName(), FormatType(e.Source), FormatFocused());
}
void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
Debug.Print("{0}, key: {1}, source: {2}, {3}", FormatMethodName(), e.Key.ToString(), FormatType(e.Source), FormatFocused());
}
// Debug output formatting helpers
string FormatFocused()
{
// show current focus and keyboard focus
return String.Format("Focus: {0}, Keyboard focus: {1}, webBrowser.HasFocusWithin: {2}",
FormatType(FocusManager.GetFocusedElement(this)),
FormatType(Keyboard.FocusedElement),
((System.Windows.Interop.IKeyboardInputSink)this.webBrowser).HasFocusWithin());
}
string FormatType(object p)
{
string result = p != null ? String.Concat('*', p.GetType().Name, '*') : "null";
if (p == this.webBrowser )
result += "!!";
return result;
}
static string FormatMethodName()
{
return new StackTrace(true).GetFrame(1).GetMethod().Name;
}
}
}
[UPDATE]The situation doesn't get better if I host WinForms WebBrowser(in place of, or side-by-side with WPF WebBrowser):
[更新]如果我托管WinForms WebBrowser(代替 WPF WebBrowser 或与 WPF WebBrowser 并列),情况不会好转:
<StackPanel Margin="20,20,20,20">
<ToggleButton Name="btnLoad" Focusable="True" IsTabStop="True" Content="Load" Click="btnLoad_Click" Width="100"/>
<WebBrowser Name="webBrowser" Focusable="True" KeyboardNavigation.IsTabStop="True" FocusVisualStyle="{x:Null}" Height="150" Margin="10,10,10,10"/>
<WindowsFormsHost Name="wfHost" Focusable="True" Height="150" Margin="10,10,10,10">
<wf:WebBrowser x:Name="wfWebBrowser" />
</WindowsFormsHost>
<Button Name="btnClose" Focusable="True" IsTabStop="True" Content="Close" Click="btnClose_Click" Width="100"/>
</StackPanel>
The only improvement is that I do see focus events on WindowsFormsHost.
唯一的改进是我确实看到了WindowsFormsHost.
[UPDATE]An extreme case: two WebBrowser controls with two carets showing at the same time:
[更新]一个极端情况:同时显示两个插入符号的两个 WebBrowser 控件:
<StackPanel Margin="20,20,20,20">
<ToggleButton Name="btnLoad" Focusable="True" IsTabStop="True" Content="Load" Click="btnLoad_Click" Width="100"/>
<WebBrowser Name="webBrowser" Focusable="True" KeyboardNavigation.IsTabStop="True" FocusVisualStyle="{x:Null}" Height="150" Margin="10,10,10,10"/>
<WebBrowser Name="webBrowser2" Focusable="True" KeyboardNavigation.IsTabStop="True" FocusVisualStyle="{x:Null}" Height="150" Margin="10,10,10,10"/>
<Button Name="btnClose" Focusable="True" IsTabStop="True" Content="Close" Click="btnClose_Click" Width="100"/>
</StackPanel>
this.webBrowser.NavigateToString("<body onload='text.focus()'><textarea id='text' style='width: 100%; height: 100%'>text</textarea></body>");
this.webBrowser2.NavigateToString("<body onload='text.focus()'><textarea id='text' style='width: 100%; height: 100%'>text2</textarea></body>");
This also illustrates that the focus handling issue is not specific to contentEditable=truecontent.
这也说明焦点处理问题并非特定于contentEditable=true内容。
回答by qJake
For anyone else stumbling upon this post and needing to set keyboardfocus to the browser control (not a particular element within the control, necessarily), this bit of code worked for me.
对于在这篇文章中绊倒并需要将键盘焦点设置到浏览器控件(不一定是控件中的特定元素)的其他任何人,这段代码对我有用。
First, add a project reference (under Extensions in VS) for Microsoft.mshtml.
首先,为Microsoft.mshtml.
Next, whenever you'd like to focus the browser control (say for example, when the Window loads), simply "focus" the HTML document:
接下来,无论何时您想要聚焦浏览器控件(例如,当窗口加载时),只需“聚焦”HTML 文档:
// Constructor
public MyWindow()
{
Loaded += (_, __) =>
{
((HTMLDocument) Browser.Document).focus();
};
}
This will place keyboard focus inside the web browser control, and inside the "invisible" ActiveX window, allowing keys like PgUp / PgDown to work on the HTML page.
这会将键盘焦点置于 Web 浏览器控件内和“不可见”ActiveX 窗口内,允许 PgUp / PgDown 等键在 HTML 页面上工作。
If you want to, you might be able to use DOM selection to find a particular element on the page, and try to focus()that particular element. I have not tried this myself.
如果您愿意,您可以使用 DOM 选择来查找页面上的特定元素,并尝试查找focus()该特定元素。我自己没有尝试过。
回答by Shawn E
The reason it behaves this way is related to the fact that it's an ActiveX control which itself is a fully windows class (it handles mouse and keyboard interaction). In fact much of the time you see the component used you'll find it is the main component taking up a full window because of this. It doesn't have to be done that way but it presents issues.
它以这种方式运行的原因与它是一个 ActiveX 控件有关,它本身是一个完全 Windows 类(它处理鼠标和键盘交互)。事实上,很多时候你看到使用的组件,你会发现它是主要组件,因此占据了一个完整的窗口。它不必那样做,但它提出了问题。
Here's a forum discussing the exact same issue and it's causes can be clarified by reading the last commentators article links:
这是一个讨论完全相同问题的论坛,可以通过阅读最后的评论员文章链接来澄清其原因:
To outline the issues you're having
概述您遇到的问题
Tabbing is misbehaving. User needs to hit Tab twice to see the caret (text cursor) inside WebBrowser and be able to type.
that's because the browser control itself is a window which can be tabbed to. It doesn't "forward" the tab to it's child elements immediately.
One way to change this would be to handle the WM message for the component itself but keep in mind that doing so gets tricky when you want the "child" document inside of it to be able to handle messages.
See: Prevent WebBrowser control from stealing focus?specifically the "answer". Although their answer doesn't account that you can control whether the component interacts through dialogs with the user by setting the Silent property (may or may not exist in the WPF control... not sure)
When user switches away from the app (e.g., with Alt-Tab), then goes back, the caret is gone and she is unable to type at all. A physical mouse click into the WebBrowser's window client area is required to get back the caret and keystrokes. This is because the control itself has received the focus. Another consideration is to add code to handle the GotFocus event and to then "change" where the focus goes. Tricky part is figuring out if this was "from" the document -> browser control or your app -> browser control. I can think of a few hacky ways to do this (variable reference based on losing focus event checked on gotfocus for example) but nothing that screams elegant.
Inconsistently, a dotted focus rectangle shows up around WebBrowser (when tabbing, but not when clicking). I could not find a way to get rid of it (FocusVisualStyle="{x:Null}" does not help). I wonder if changing Focusable would help or hinder. Never tried it but I'm going to venture a guess that if it did work it would stop it from being keyboard navigable at all.
Internally, WebBrowser never receives the focus. That's true for both logical focus (FocusManager) and input focus (Keyboard). The Keyboard.GotKeyboardFocusEvent and FocusManager.GotFocusEvent events never get fired for WebBrowser (although they both do for buttons in the same focus scope). Even when the caret is inside WebBrowser, FocusManager.GetFocusedElement(mainWindow) points to a previously focused element (a button) and Keyboard.FocusedElement is null. At the same time, ((IKeyboardInputSink)this.webBrowser).HasFocusWithin() returns true. People have hit issues with where 2 browser controls both show the focus(well... the caret) or even had a hidden control take the focus.
标签是行为不端。用户需要点击 Tab 两次才能看到 WebBrowser 中的插入符号(文本光标)并能够输入。
那是因为浏览器控件本身是一个可以选项卡的窗口。它不会立即将选项卡“转发”到它的子元素。
改变这种情况的一种方法是处理组件本身的 WM 消息,但请记住,当您希望其中的“子”文档能够处理消息时,这样做会变得棘手。
请参阅:防止 WebBrowser 控件窃取焦点?特别是“答案”。尽管他们的回答没有考虑到您可以通过设置 Silent 属性来控制组件是否通过对话框与用户交互(WPF 控件中可能存在也可能不存在......不确定)
当用户离开应用程序(例如,使用 Alt-Tab),然后返回时,插入符号消失了,她根本无法输入。需要物理鼠标单击 WebBrowser 的窗口客户区才能恢复插入符号和击键。这是因为控件本身已获得焦点。另一个考虑是添加代码来处理 GotFocus 事件,然后“更改”焦点所在的位置。棘手的部分是弄清楚这是“来自”文档-> 浏览器控件还是您的应用程序-> 浏览器控件。我可以想到一些 hacky 方法来做到这一点(例如,基于在 gotfocus 上检查的失去焦点事件的变量引用),但没有什么是优雅的。
不一致的是,一个虚线焦点矩形出现在 WebBrowser 周围(当 Tab 键时,而不是在单击时)。我找不到摆脱它的方法(FocusVisualStyle="{x:Null}" 没有帮助)。我想知道改变 Focusable 是有帮助还是有阻碍。从未尝试过,但我要冒险猜测,如果它确实有效,它将完全阻止它通过键盘导航。
在内部,WebBrowser 永远不会获得焦点。对于逻辑焦点 (FocusManager) 和输入焦点 (Keyboard) 都是如此。Keyboard.GotKeyboardFocusEvent 和 FocusManager.GotFocusEvent 事件永远不会为 WebBrowser 触发(尽管它们都为同一焦点范围内的按钮触发)。即使插入符号在 WebBrowser 内,FocusManager.GetFocusedElement(mainWindow) 也指向先前聚焦的元素(按钮),并且 Keyboard.FocusedElement 为空。同时, ((IKeyboardInputSink)this.webBrowser).HasFocusWithin() 返回 true。人们遇到了两个浏览器控件都显示焦点(好吧......插入符号)甚至隐藏控件占据焦点的问题。
All in all it's pretty awesome what you can do with the component but it's just the right mix of letting you control/change the behavior along with predefined sets of behavior to be maddening.
总而言之,您可以使用该组件执行的操作非常棒,但它只是让您控制/更改行为以及令人发狂的预定义行为集的正确组合。
My suggestion would be to try to subclass the messages so you can direct the focus control directly through code and bypass it's window from trying to do so.
我的建议是尝试对消息进行子类化,以便您可以直接通过代码引导焦点控制并绕过它的窗口,以免尝试这样做。

