首页 文章

WebBrowser控制键盘和焦点行为

提问于
浏览
27

显然,WPF WebBrowser control有一些严重的键盘和焦点问题 . 我整理了一个简单的WPF应用程序,只是一个WebBrowser和两个按钮 . 该应用程序加载一个非常基本的可编辑HTML标记( <body contentEditable='true'>some text</body> )并演示以下内容:

  • Tabbing行为不端 . 用户需要按两次Tab键才能在WebBrowser中查看插入符号(文本光标)并能够键入 .

  • 当用户离开应用程序时(例如,使用Alt-Tab),然后返回,插入符号消失,她根本无法键入 . 需要物理鼠标单击WebBrowser的窗口客户端区域才能取回插入符号和击键 .

  • 不一致地,一个虚线的焦点矩形显示在WebBrowser周围(当标签时,但不是点击时) . 我找不到摆脱它的方法( FocusVisualStyle="{x:Null}" 没有帮助) .

  • 在内部,WebBrowser永远不会获得焦点 . 对于逻辑焦点(FocusManager)和输入焦点(Keyboard)都是如此 . Keyboard.GotKeyboardFocusEventFocusManager.GotFocusEvent 事件永远不会被WebBrowser触发(尽管它们都针对同一焦点范围内的按钮) . 即使插入符号在WebBrowser中, FocusManager.GetFocusedElement(mainWindow) 指向先前聚焦的元素(按钮), Keyboard.FocusedElementnull . 同时, ((IKeyboardInputSink)this.webBrowser).HasFocusWithin() 返回 true .

我'd say, such behaviour is almost too dysfunctional to be true, but that'是如何运作的 . 我可能会想出一些修复它的hack并将其与原始WPF控件(如 TextBox )一起排成一行 . 我仍然希望,也许我在这里错过了一些模糊而简单的东西 . 有没有人处理过类似的问题?任何有关如何解决这个问题的建议将不胜感激 .

此时,我倾向于基于HwndHost为WebBrowser ActiveX Control开发内部WPF包装器 . 我们也是WebBrowser的 considering other alternatives ,例如Chromium Embedded Framework(CEF) .

VS2012项目可以从here下载,以防有人想玩它 .

这是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;
        }

    }
}

[UPDATE] 如果我托管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 上看到焦点事件 .

[UPDATE] 极端情况:两个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 回答

  • 5

    它以这种方式运行的原因与它是一个ActiveX控件这一事实有关,它本身就是一个完全的Windows类(它处理鼠标和键盘交互) . 实际上,在大多数情况下,当您看到使用的组件时,您会发现它是占用整个窗口的主要组件 . 它不必这样做,但它提出了问题 .

    这是一个讨论完全相同问题的论坛,通过阅读上一篇评论员的文章链接可以澄清其原因:

    http://social.msdn.microsoft.com/Forums/vstudio/en-US/1b50fec6-6596-4c0a-9191-32cd059f18f7/focus-issues-with-systemwindowscontrolswebbrowser

    概述您遇到的问题

    • Tabbing行为不端 . 用户需要按两次Tab键才能在WebBrowser中查看插入符号(文本光标)并能够键入 .

    那是因为浏览器控件本身就是一个可以选中的窗口 . 它不会立即将标签“转发”到它的子元素 .

    改变这种情况的一种方法是处理组件本身的WM消息,但请记住,当您希望其中的“子”文档能够处理消息时,这样做会变得棘手 .

    见:Prevent WebBrowser control from stealing focus?具体是"answer" . 虽然他们的答案没有说明您可以通过设置Silent属性来控制组件是否通过对话框与用户进行交互(WPF控件中可能存在也可能不存在...不确定)

    • 当用户离开应用程序时(例如,使用Alt-Tab),然后返回,插入符号消失,她根本无法键入 . 需要物理鼠标单击WebBrowser的窗口客户端区域才能取回插入符号和击键 . 这是因为控件本身已经得到了关注 . 另一个考虑因素是添加代码来处理GotFocus事件,然后“改变”焦点所在的位置 . 棘手的部分是弄清楚这是“来自”文档 - >浏览器控件或你的应用程序 - >浏览器控件 . 我可以想到一些hacky方法来做到这一点(基于丢失焦点事件的变量引用,例如在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 . 人们已经遇到了问题,其中两个浏览器控件都显示焦点(好吧......插入符号)或者甚至有一个隐藏控件来关注焦点 .

    总而言之,你可以用组件做的非常棒,但它只是让你控制/改变行为以及预定义的行为组合令人抓狂的正确组合 .

    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.

  • 4

    对于其他任何绊倒这篇文章并且需要将键盘焦点设置到浏览器控件(不一定是控件中的特定元素)的人来说,这段代码对我有用 .

    首先,为 Microsoft.mshtml 添加项目引用(在VS中的Extensions下) .

    接下来,每当您想要关注浏览器控件时(例如,当窗口加载时),只需“聚焦”HTML文档:

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

    这会将键盘焦点放在Web浏览器控件中,并在“不可见”ActiveX窗口内,允许像PgUp / PgDown这样的键在HTML页面上工作 .

    如果您愿意,您可以使用DOM选择来查找页面上的特定元素,并尝试 focus() 该特定元素 . 我自己没试过 .

相关问题