Управляющая клавиатура WebBrowser и поведение фокуса
По-видимому, есть серьезные проблемы с клавиатурой и фокусом с WPF WebBrowser control. Я собрал тривиальное приложение WPF, просто WebBrowser и две кнопки. Приложение загружает очень базовую редактируемую разметку HTML (<body contentEditable='true'>some text</body>
) и демонстрирует следующее:
-
Вкладка не работает. Пользователю нужно дважды нажать клавишу Tab, чтобы увидеть курсор (текстовый курсор) внутри WebBrowser и иметь возможность печатать.
-
Когда пользователь отключается от приложения (например, с помощью Alt-Tab), затем возвращается назад, каретка исчезает, и она не может набрать вообще. Физический щелчок мышью в области клиента окна WebBrowser необходим, чтобы вернуть каретку и нажатия клавиш.
-
Несомненно, прямоугольник с точками фокусировки отображается вокруг WebBrowser (при табуляции, но не при нажатии). Я не мог найти способ избавиться от него (FocusVisualStyle="{x:Null}"
не помогает).
-
Внутри WebBrowser никогда не получает фокуса. Это верно для обоих логических фокусов (FocusManager) и фокус ввода (Keyboard). События Keyboard.GotKeyboardFocusEvent
и FocusManager.GotFocusEvent
никогда не запускаются для WebBrowser (хотя оба они делают для кнопок в той же области фокуса). Даже если каретка находится внутри WebBrowser, FocusManager.GetFocusedElement(mainWindow)
указывает на ранее сфокусированный элемент (кнопка), а Keyboard.FocusedElement
- null
. В то же время ((IKeyboardInputSink)this.webBrowser).HasFocusWithin()
возвращает true
.
Я бы сказал, что такое поведение почти слишком дисфункционально, чтобы быть правдой, но как это работает. Возможно, я мог бы придумать некоторые хаки, чтобы исправить это и привести его в ряд с встроенными элементами управления WPF, например TextBox
. Тем не менее, я надеюсь, может быть, я пропустил что-то неясное, но все же простое здесь. Кто-нибудь имел дело с подобной проблемой? Любые предложения о том, как исправить это, будут очень признательны.
На этом этапе я склонен разрабатывать внутреннюю оболочку WPF для WebBrowser ActiveX Control на основе HwndHost. Мы также рассматриваем другие альтернативы для 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>
Это код С#, он содержит множество диагностических трасс, чтобы показать, как маршрутизируются события фокуса/клавиатуры и где находится фокус:
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] Ситуация не улучшается, если я размещаю WinForms 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
.
Ответы
Ответ 1
Причина, по которой он ведет себя таким образом, связана с тем, что это элемент управления ActiveX, который сам по себе является полностью классом Windows (он обрабатывает взаимодействие с мышью и клавиатурой). На самом деле большую часть времени, когда вы видите используемый компонент, вы обнаружите, что из-за этого основной компонент занимает полное окно. Это не должно быть сделано таким образом, но оно представляет проблемы.
Здесь форум, обсуждающий ту же самую проблему и ее причины, можно прояснить, прочитав последние ссылки статей комментаторов:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/1b50fec6-6596-4c0a-9191-32cd059f18f7/focus-issues-with-systemwindowscontrolswebbrowser
Очертить проблемы, которые у вас есть
-
Вкладка не работает. Пользователю нужно дважды нажать клавишу Tab, чтобы увидеть курсор (текстовый курсор) внутри WebBrowser и иметь возможность печатать.
потому что сам браузер - это окно, на которое можно вставить вкладку. Он не "сразу же" переводит вкладку в дочерние элементы.
Один из способов изменить это - обработать сообщение WM для самого компонента, но имейте в виду, что это становится сложным, когда вы хотите, чтобы "дочерний" документ внутри него мог обрабатывать сообщения.
Смотрите: Предотвратите контроль WebBrowser от кражи фокуса?, в частности, "ответ". Хотя их ответ не учитывает, что вы можете контролировать, взаимодействует ли компонент через диалоги с пользователем, установив свойство Silent (может или не может существовать в элементе управления WPF... не уверен)
-
Когда пользователь отключается от приложения (например, с помощью Alt-Tab), затем возвращается назад, каретка исчезает, и она не может набрать вообще. Физический щелчок мышью в клиентской области окна WebBrowser необходим для возврата каретки и нажатия клавиш.
Это связано с тем, что сам контроль получил фокус. Другое соображение заключается в том, чтобы добавить код для обработки события GotFocus и затем "сменить", где сосредоточен фокус. Трудная часть выясняет, был ли это "из" элемента управления браузером document → browser или вашего приложения → браузера. Я могу придумать несколько хакерских способов сделать это (переменная ссылка, основанная на потере фокусного события, например, на gotfocus), но ничего, что кричит элегантно.
-
Несомненно, прямоугольник с точками фокусировки отображается вокруг WebBrowser (при табуляции, но не при нажатии). Я не мог найти способ избавиться от него (FocusVisualStyle = "{x: Null}" не помогает).
Интересно, поможет ли изменение Focusable или помешает. Никогда не пробовал, но я собираюсь рискнуть предположить, что если бы это сработало, это остановило бы его от навигации на клавиатуре вообще.
-
Внутри WebBrowser никогда не получает фокуса. Это верно как для логической фокусировки (FocusManager), так и для фокуса ввода (клавиатура). События Keyboard.GotKeyboardFocusEvent и FocusManager.GotFocusEvent никогда не запускаются для WebBrowser (хотя оба они делают для кнопок в той же области фокуса). Даже когда каретка находится внутри WebBrowser, FocusManager.GetFocusedElement(mainWindow) указывает на ранее сфокусированный элемент (кнопка) и Keyboard.FocusedElement равен null. В то же время ((IKeyboardInputSink) this.webBrowser).HasFocusWithin() возвращает true.
Люди сталкиваются с проблемами, когда два браузера контролируют как отображение фокуса (ну... карет), либо даже скрытый контроль фокусируется.
В целом, это довольно удивительно, что вы можете сделать с компонентом, но это просто правильное сочетание того, чтобы вы могли контролировать/изменять поведение вместе с предопределенными наборами поведения, чтобы сходить с ума.
Мое предложение состояло в том, чтобы попытаться подклассифицировать сообщения, чтобы вы могли напрямую управлять фокусом через код и обойти это окно, пытаясь сделать это.
Ответ 2
Для кого-то, кто наткнулся на это сообщение и ему нужно настроить фокус клавиатуры на элемент управления браузера (не обязательно на элемент внутри элемента управления, обязательно), этот бит кода работал у меня.
Сначала добавьте ссылку на проект (в разделе "Расширения в VS" ) для Microsoft.mshtml
.
Далее, всякий раз, когда вы хотите сфокусировать управление браузером (скажем, например, когда загружается окно), просто "сфокусируйте" документ HTML:
// Constructor
public MyWindow()
{
Loaded += (_, __) =>
{
((HTMLDocument) Browser.Document).focus();
};
}
Это позволит разместить фокус клавиатуры внутри элемента управления веб-браузера и внутри "невидимого" окна ActiveX, позволяя таким функциям, как PgUp/PgDown, работать с HTML-страницей.
Если вы хотите, вы можете использовать выбор DOM, чтобы найти определенный элемент на странице, и попробуйте focus()
этот конкретный элемент. Я сам этого не пробовал.