WebBrowser control keyboard and focus behavior
I've made up a trivial WPF app, just a WebBrowser control and two buttons.
The app loads a very basic editable HTML markup (<body
contentEditable='true'>some text</body>) and demonstrates some serious
keyboard and focus issue with WPF WebBrowser:
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 client area is required to get back the caret and
keystrokes.
A dotted focus rectangle is inconsistently shown 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, the WebBrowser control never receives focus (nether
FocusManager nor Keyboard variations of WPF focus). The
Keyboard.GotKeyboardFocusEvent and FocusManager.GotFocusEvent events never
get fired for WebBrowser. Even when the caret is inside WebBrowser,
FocusManager.GetFocusedElement(mainWindow) points to a previously focused
element (button) and Keyboard.FocusedElement is null. At the same time,
((IKeyboardInputSink)this.webBrowser).HasFocusWithin() returns true.
I'd say, such behaviour is almost too dysfunctional to be true, but that's
how it works. I could probably use a set of hacks to fix it and bring it
in row with native WPF controls like TextBox. Yet I hope, maybe I'm
missing something obscure but simple here. Has anyone dealt with a similar
problem? Any suggestions on how to fix this would be greatly appreciated.
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 alternatives to WebBrowser, such as Chromium Embedded Framework
(CEF).
The VS2012 project can be downloaded from here in case someone wants to
play with it.
This is 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:
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;
}
}
}
Thank you.
No comments:
Post a Comment