显然, WPF WebBrowser 控件存在一些严重的键盘和焦点问题。我已经组合了一个简单的 WPF 应用程序,只有一个 WebBrowser 和两个按钮。该应用程序加载了一个非常基本的可编辑 HTML 标记 ( <body contentEditable='true'>some text</body>
) 并演示了以下内容:
制表符行为不端。用户需要按两次 Tab 才能看到 WebBrowser 中的插入符号(文本光标)并能够键入。
当用户离开应用程序(例如,使用 Alt-Tab)然后返回时,插入符号消失了,她根本无法打字。需要物理鼠标单击 WebBrowser 的窗口客户区才能取回插入符号和击键。
不一致的是,在 WebBrowser 周围出现了一个虚线焦点矩形(在选项卡时,但在单击时不显示)。我找不到摆脱它的方法(
FocusVisualStyle="{x:Null}"
没有帮助)。在内部,WebBrowser 从不接收焦点。这对于逻辑焦点 ( FocusManager ) 和输入焦点 ( Keyboard ) 都是如此。和事件永远不会为 WebBrowser 触发
Keyboard.GotKeyboardFocusEvent
(FocusManager.GotFocusEvent
尽管它们都为同一焦点范围内的按钮触发)。即使插入符号位于 WebBrowser 内,它也FocusManager.GetFocusedElement(mainWindow)
指向先前聚焦的元素(按钮)并且Keyboard.FocusedElement
是null
. 同时,((IKeyboardInputSink)this.webBrowser).HasFocusWithin()
返回true
。
我想说,这样的行为几乎是功能失调的,不可能是真的,但这就是它的工作原理。我可能会想出一些技巧来修复它,并将它与原生 WPF 控件(如TextBox
. 我仍然希望,也许我在这里遗漏了一些晦涩但简单的东西。有没有人处理过类似的问题?任何有关如何解决此问题的建议将不胜感激。
在这一点上,我倾向于基于HwndHost为 WebBrowser ActiveX 控件开发一个内部 WPF 包装器。我们还在考虑 WebBrowser 的其他替代方案,例如 Chromium Embedded Framework (CEF)。
可以从这里下载 VS2012 项目,以防有人想玩它。
这是 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>
这是 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;
}
}
}
[更新]如果我托管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>
唯一的改进是我确实在WindowsFormsHost
.
[更新]一个极端情况:两个 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>");
这也说明焦点处理问题并非特定于contentEditable=true
内容。