Клавиатура управления WebBrowser и поведение фокуса

По-видимому, существуют некоторые серьезные проблемы с клавиатурой и фокусом в элементе управления WPF WebBrowser. Я собрал тривиальное приложение WPF, просто веб-браузер и две кнопки. Приложение загружает очень простую редактируемую разметку HTML (<body contentEditable='true'>some text</body>) и демонстрирует следующее:

  • Таббинг плохо себя ведет. Пользователь должен дважды нажать Tab, чтобы увидеть курсор (текстовый курсор) внутри WebBrowser и иметь возможность печатать.

  • Когда пользователь отключается от приложения (например, с помощью Alt-Tab), а затем возвращается, курсор исчезает, и она вообще не может печатать. Физический щелчок мыши в клиентской области окна WebBrowser необходим для возврата каретки и нажатия клавиш.

  • Непоследовательно, пунктирный фокусный прямоугольник появляется вокруг WebBrowser (при вкладке, но не при нажатии). Я не мог найти способ избавиться от этого (FocusVisualStyle="{x:Null}" не помогает).

  • Внутренне WebBrowser никогда не получает фокус. Это верно как для логического фокуса ( FocusManager), так и для фокуса ввода ( клавиатура). Keyboard.GotKeyboardFocusEvent а также FocusManager.GotFocusEvent события никогда не запускаются для WebBrowser (хотя они оба делают для кнопок в одной и той же области фокусировки). Даже когда каретка находится внутри WebBrowser, FocusManager.GetFocusedElement(mainWindow) указывает на ранее сфокусированный элемент (кнопку) и Keyboard.FocusedElement является null, В то же время, ((IKeyboardInputSink)this.webBrowser).HasFocusWithin() возвращается true,

Я бы сказал, что такое поведение почти не работает, чтобы быть правдой, но так оно и работает. Я мог бы, вероятно, придумать несколько хаков, чтобы исправить это и привести его в соответствие с родными элементами управления WPF, такими как TextBox, Тем не менее, я надеюсь, может быть, я упускаю что-то темное, но простое здесь Кто-нибудь имел дело с подобной проблемой? Будем весьма благодарны за любые предложения о том, как это исправить.

На данный момент я склонен к разработке собственной оболочки WPF для элемента управления ActiveX WebBrowser, основанной на 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>

Это код 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):

<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 содержание.

2 ответа

Для любого, кто наткнулся на этот пост и нуждается в настройке фокуса клавиатуры для элемента управления браузера (не обязательно для конкретного элемента в элементе управления), этот фрагмент кода работал для меня.

Сначала добавьте ссылку на проект (в разделе Расширения в VS) для Microsoft.mshtml,

Далее, всякий раз, когда вы хотите сфокусировать элемент управления браузера (например, при загрузке окна), просто "сфокусируйте" документ HTML:

// Constructor
public MyWindow()
{
    Loaded += (_, __) =>
    {
        ((HTMLDocument) Browser.Document).focus();
    };
}

Это поместит фокус клавиатуры внутри элемента управления веб-браузера и внутри "невидимого" окна ActiveX, позволяя таким клавишам, как PgUp / PgDown, работать на HTML-странице.

Если вы хотите, вы можете использовать выбор DOM, чтобы найти определенный элемент на странице, и попытаться focus() этот конкретный элемент. Я не пробовал это сам.

Причина, по которой он так себя ведет, связана с тем, что это элемент управления ActiveX, который сам по себе является полностью оконным классом (он обрабатывает взаимодействие мыши и клавиатуры). Фактически, большую часть времени, когда вы видите используемый компонент, вы обнаруживаете, что это основной компонент, занимающий полное окно из-за этого. Это не должно быть сделано таким образом, но это создает проблемы.

Вот форум, обсуждающий ту же самую проблему, и ее причины можно выяснить, прочитав ссылки на последние статьи комментаторов:

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 и затем "изменить" направление фокусировки. Сложно выяснить, было ли это "из" документа -> элемент управления браузером или ваше приложение -> элемент управления браузером. Я могу придумать несколько хакерских способов сделать это (например, ссылка на переменную, основанная на потере события фокуса, проверенной на 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 элемента управления браузера отображали фокус (ну... каретка) или даже скрытый элемент управления фокусировался.

В целом, это довольно круто, что вы можете сделать с компонентом, но это просто правильное сочетание, позволяющее вам контролировать / изменять поведение вместе с предопределенными наборами поведения, которые сводят с ума.

Мое предложение состоит в том, чтобы попытаться разделить сообщения на подклассы, чтобы вы могли направлять управление фокусом непосредственно через код и обходить его окно от попыток сделать это.

Другие вопросы по тегам