WebBrowser控制键盘和焦点行为 [英] WebBrowser control keyboard and focus behavior

查看:301
本文介绍了WebBrowser控制键盘和焦点行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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




  • 标签行为不当。用户需要点击Tab两次才能看到WebBrowser中的插入符号(文本光标)并能够输入。

  • 当用户从应用程序切换(例如,与Alt-Tab),然后回去,脱字符不见了,她根本无法打字。一个物理鼠标点击进入WebBrowser的窗口客户区域,需要取回插入符号和击键。
  • 不一致的是,在WebBrowser周围出现一个虚线的焦点矩形标签,而不是点击时)。我无法找到摆脱它的方法( FocusVisualStyle ={x:Null}不起作用)


  • 在内部,WebBrowser永远不会收到焦点。对于逻辑焦点来说都是如此( FocusManager )和输入焦点(键盘)。 WebBrowser中的 Keyboard.GotKeyboardFocusEvent FocusManager.GotFocusEvent 事件永远不会被触发(尽管它们都是为按钮在相同的焦点范围内)。即使当插入符在WebBrowser中时, FocusManager.GetFocusedElement(mainWindow)指向一个以前的焦点元素(一个按钮)和 Keyboard.FocusedElement null 。同时,((IKeyboardInputSink)this.webBrowser).HasFocusWithin()返回 true




我想说,这样的行为几乎无法实现,但这就是它的工作原理。我可能会想出一些黑客来解决它,并与本地WPF控件,如 TextBox 。不过,我希望,也许我在这里错过了一些晦涩难懂的东西。有没有人处理过类似的问题?任何建议如何解决这个将不胜感激。

在这一点上,我倾向于开发一个WebBrowser ActiveX控件的内部WPF包装,基于 HwndHost 。我们也是 考虑其他可以从Web浏览器(如Chromium嵌入式框架(CEF))中选择其他方法

可以从这里,以防有人想玩。



这是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 =MainWindowWidth =640Height =480Background =LightGray>

< StackPanel Margin =20,20,20,20>
< ToggleButton Name =btnLoadFocusable =TrueIsTabStop =TrueContent =LoadClick =btnLoad_ClickWidth =100/>

< WebBrowser Name =webBrowserFocusable =TrueKeyboardNavigation.IsTabStop =TrueFocusVisualStyle ={x:Null}Height =300/>

< Button Name =btnCloseFocusable =TrueIsTabStop =TrueContent =CloseClick =btnClose_ClickWidth =100/>
< / StackPanel>

< / Window>

这是C#代码,它有一堆诊断痕迹来显示焦点/键盘事件如何路由以及重点是:

  using System; 
使用System.Diagnostics;
使用System.Reflection;
使用System.Runtime.InteropServices;
使用System.Threading;
使用System.Windows;
使用System.Windows.Input;
使用System.Windows.Navigation;

命名空间WpfWebBrowserTest

公共部分类MainWindow窗
$ b $ public MainWindow

InitializeComponent() ;

//观察这些事件进行诊断
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)
{
//载入浏览器
this.webBrowser.NavigateToString(< body contentEditable ='true'onload ='focus()'> Line 1< br> Line 3< / body>);
this.btnLoad.IsChecked = true;

$ b $ private void btnClose_Click(object sender,RoutedEventArgs e)
{
//关闭表单
if(MessageBox.Show(Close it ?,this.Title,MessageBoxButton.YesNo)== MessageBoxResult.Yes)
this.Close();

$ b $ //诊断事件

MainWindow_GotKeyboardFocus(object sender,KeyboardFocusChangedEventArgs e)
{
Debug.Print({0 },源:{1},{2},FormatMethodName(),FormatType(e.Source),FormatFocused());

$ b $ 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 FormatMethodName(),e.​​Key.ToString(),FormatType(e.Source),FormatFocused());


//调试输出格式助手

字符串FormatFocused()
{
//显示当前焦点和键盘焦点
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());


字符串FormatType(对象p)
{
字符串结果= p!= null? String.Concat('*',p.GetType()。Name,'*'):null;
if(p == this.webBrowser)
result + =!!;
返回结果;


静态字符串FormatMethodName()
{
返回新的StackTrace(true).GetFrame(1).GetMethod()。Name;





$ b $ p $强> [UPDATE] 如果我托管 WinForms WebBrowser (代替或与WPF WebBrowser并排):
$ b $ p

 < StackPanel Margin =20,20,20,20> 
< ToggleButton Name =btnLoadFocusable =TrueIsTabStop =TrueContent =LoadClick =btnLoad_ClickWidth =100/>

< WebBrowser Name =webBrowserFocusable =TrueKeyboardNavigation.IsTabStop =TrueFocusVisualStyle ={x:Null}Height =150Margin =10,10,10 ,10\" />

< WindowsFormsHost Name =wfHostFocusable =TrueHeight =150Margin =10,10,10,10>
< wf:WebBrowser x:Name =wfWebBrowser/>
< / WindowsFormsHost>

< Button Name =btnCloseFocusable =TrueIsTabStop =TrueContent =CloseClick =btnClose_ClickWidth =100/>
< / StackPanel>



唯一的改进是我在 WindowsFormsHost 中看到焦点事件。


$ b [UPDATE] 极端情况: WebBrowser控件同时显示两个插入符号:

 < StackPanel Margin =20,20,20,20> ; 
< ToggleButton Name =btnLoadFocusable =TrueIsTabStop =TrueContent =LoadClick =btnLoad_ClickWidth =100/>

< WebBrowser Name =webBrowserFocusable =TrueKeyboardNavigation.IsTabStop =TrueFocusVisualStyle ={x:Null}Height =150Margin =10,10,10 ,10\" />
< WebBrowser Name =webBrowser2Focusable =TrueKeyboardNavigation.IsTabStop =TrueFocusVisualStyle ={x:Null}Height =150Margin =10,10,10,10 >

< Button Name =btnCloseFocusable =TrueIsTabStop =TrueContent =CloseClick =btnClose_ClickWidth =100/>
< / StackPanel>

this.webBrowser.NavigateToString(< body onload ='text.focus()'>< textarea id ='text'style ='width:100%; height:100% >文字及LT; / textarea的>< /体>中);
this.webBrowser2.NavigateToString(< body onload ='text.focus()'>< textarea id ='text'style ='width:100%; height:100%'> text2< / textarea的>< /体>中);

这也说明焦点处理问题不是特定于 contentEditable = true content。

解决方案

这样做的原因是因为它是一个ActiveX控件本身是一个完全的Windows类(它处理鼠标和键盘的交互)。事实上,大部分时间你看到使用的组件,你会发现它是占据一个完整的窗口,因为这个主要组件。这不是必须这样做,但它提出了问题。



这是一个讨论完全相同的问题的论坛,它的原因可以通过阅读最后评论员文章链接:

http://social.msdn.microsoft.com/Forums/vstudio/zh-CN/1b50fec6-6596-4c0a-9191-32cd059f18f7/focus-issues使用systemwindowscontrolswebbrowser



概述您遇到的问题

>

总而言之,你可以用组件做什么,但它是正确的组合,让你控制/改变行为以及预定义的行为组合。



我的建议是尝试对消息进行子类化,以便直接通过代码指导焦点控制,并避免尝试这样做。 strong>


Apparently, there are some serious keyboard and focus issues with WPF WebBrowser control. I've put together a trivial WPF app, just a WebBrowser and two buttons. The app loads a very basic editable HTML markup (<body contentEditable='true'>some text</body>) and demonstrates the following:

  • 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 window client area is required to get back the caret and keystrokes.

  • Inconsistently, a dotted focus rectangle shows up 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, WebBrowser never receives the focus. That's true for both logical focus (FocusManager) and input focus (Keyboard). The Keyboard.GotKeyboardFocusEvent and FocusManager.GotFocusEvent events never get fired for WebBrowser (although they both do for buttons in the same focus scope). Even when the caret is inside WebBrowser, FocusManager.GetFocusedElement(mainWindow) points to a previously focused element (a 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 come up with some hacks to fix it and bring it in row with native WPF controls like TextBox. Still I hope, maybe I'm missing something obscure yet 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;
        }

    }
}

[UPDATE] The situation doesn't get better if I host WinForms WebBrowser (in place of, or side-by-side with 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>

The only improvement is that I do see focus events on WindowsFormsHost.

[UPDATE] An extreme case: two WebBrowser controls with two carets showing at the same time:

<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>");

This also illustrates that the focus handling issue is not specific to contentEditable=true content.

解决方案

The reason it behaves this way is related to the fact that it's an ActiveX control which itself is a fully windows class (it handles mouse and keyboard interaction). In fact much of the time you see the component used you'll find it is the main component taking up a full window because of this. It doesn't have to be done that way but it presents issues.

Here's a forum discussing the exact same issue and it's causes can be clarified by reading the last commentators article links:

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

To outline the issues you're having

  • Tabbing is misbehaving. User needs to hit Tab twice to see the caret (text cursor) inside WebBrowser and be able to type.

    that's because the browser control itself is a window which can be tabbed to. It doesn't "forward" the tab to it's child elements immediately.

    One way to change this would be to handle the WM message for the component itself but keep in mind that doing so gets tricky when you want the "child" document inside of it to be able to handle messages.

    See: Prevent WebBrowser control from stealing focus? specifically the "answer". Although their answer doesn't account that you can control whether the component interacts through dialogs with the user by setting the Silent property (may or may not exist in the WPF control... not sure)

  • 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 window client area is required to get back the caret and keystrokes. This is because the control itself has received the focus. Another consideration is to add code to handle the GotFocus event and to then "change" where the focus goes. Tricky part is figuring out if this was "from" the document -> browser control or your app -> browser control. I can think of a few hacky ways to do this (variable reference based on losing focus event checked on gotfocus for example) but nothing that screams elegant.

  • Inconsistently, a dotted focus rectangle shows up 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). I wonder if changing Focusable would help or hinder. Never tried it but I'm going to venture a guess that if it did work it would stop it from being keyboard navigable at all.

  • Internally, WebBrowser never receives the focus. That's true for both logical focus (FocusManager) and input focus (Keyboard). The Keyboard.GotKeyboardFocusEvent and FocusManager.GotFocusEvent events never get fired for WebBrowser (although they both do for buttons in the same focus scope). Even when the caret is inside WebBrowser, FocusManager.GetFocusedElement(mainWindow) points to a previously focused element (a button) and Keyboard.FocusedElement is null. At the same time, ((IKeyboardInputSink)this.webBrowser).HasFocusWithin() returns true. People have hit issues with where 2 browser controls both show the focus(well... the caret) or even had a hidden control take the focus.

All in all it's pretty awesome what you can do with the component but it's just the right mix of letting you control/change the behavior along with predefined sets of behavior to be maddening.

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.

这篇关于WebBrowser控制键盘和焦点行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆