web-dev-qa-db-ja.com

WebBrowserはキーボードとフォーカスの動作を制御します

どうやら、 WPF WebBrowserコントロール にはいくつかの深刻なキーボードとフォーカスの問題があります。ささいなWPFアプリ、WebBrowserと2つのボタンだけをまとめました。アプリは非常に基本的な編集可能なHTMLマークアップ(_<body contentEditable='true'>some text</body>_)をロードし、次のことを示します。

  • タブの動作がおかしい。 WebBrowser内のキャレット(テキストカーソル)を表示して入力できるようにするには、ユーザーはTabキーを2回押す必要があります。

  • ユーザーがアプリから離れて(たとえば、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を返します。

私は、そのような振る舞いはほとんど機能不全であり、真実ではないと思いますが、それがどのように機能するかです。私はおそらくそれを修正し、TextBoxのようなネイティブWPFコントロールと並べるためにいくつかのハックを思い付くことができます。それでも私は願っています、多分私はここで曖昧でありながら単純な何かを見逃しています。誰かが同様の問題に対処しましたか?これを修正する方法についての提案は大歓迎です。

この時点で、 HwndHost に基づいて、WebBrowser ActiveXControl用の社内WPFラッパーを開発する傾向があります。また、Chromium Embedded Framework(CEF)などのWebBrowserに対して他の代替案を検討中です。

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

    }
}
_

[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にフォーカスイベントが表示されることです。

[UPDATE]極端な場合:2つのキャレットが同時に表示されている2つの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_コンテンツに固有のものではないことも示しています。

28
noseratio

この投稿に出くわし、keyboardフォーカスをブラウザコントロール(必ずしもコントロール内の特定の要素ではない)に設定する必要がある他の人にとって、このコードは私のために機能しました。

まず、_Microsoft.mshtml_のプロジェクト参照(VSの拡張機能の下)を追加します。

次に、ブラウザコントロールにフォーカスしたいときはいつでも(たとえば、ウィンドウがロードされたとき)、HTMLドキュメントに「フォーカス」するだけです。

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

これにより、キーボードフォーカスがWebブラウザコントロール内および「非表示」ActiveXウィンドウ内に配置され、PgUp/PgDownなどのキーがHTMLページで機能できるようになります。

必要に応じて、DOM選択を使用してページ上の特定の要素を検索し、その特定の要素をfocus()しようとすることができる場合があります。私はこれを自分で試したことがありません。

7
qJake

このように動作する理由は、それ自体が完全にWindowsクラスであるActiveXコントロールであるという事実に関連しています(マウスとキーボードの相互作用を処理します)。実際、使用されているコンポーネントを見ると、このためにウィンドウ全体を占めるメインコンポーネントであることがわかります。そのようにする必要はありませんが、問題が発生します。

これはまったく同じ問題を議論しているフォーラムであり、その原因は最後のコメンテーターの記事のリンクを読むことで明らかにすることができます:

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

あなたが抱えている問題の概要を説明する

  • タブの動作がおかしい。 WebBrowser内のキャレット(テキストカーソル)を表示して入力できるようにするには、ユーザーはTabキーを2回押す必要があります。

    これは、ブラウザコントロール自体がタブで移動できるウィンドウであるためです。タブをその子要素にすぐに「転送」するわけではありません。

    これを変更する1つの方法は、コンポーネント自体のWMメッセージを処理することですが、コンポーネント内の「子」ドキュメントでメッセージを処理できるようにする場合は、注意が必要です。

    参照: WebBrowserコントロールがフォーカスを盗むのを防ぎますか? 具体的には「答え」。彼らの答えは、Silentプロパティを設定することでコンポーネントがユーザーとのダイアログを介して対話するかどうかを制御できることを説明していません(WPFコントロールに存在する場合と存在しない場合があります...わからない)

  • ユーザーがアプリから離れて(たとえば、Alt-Tabを使用して)戻ってから戻ると、キャレットがなくなり、入力できなくなります。キャレットとキーストロークを元に戻すには、WebBrowserのウィンドウクライアント領域をマウスで物理的にクリックする必要があります。これは、コントロール自体がフォーカスを受け取ったためです。もう1つの考慮事項は、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つのブラウザコントロールの両方がフォーカスを表示する(まあ...キャレット)か、非表示のコントロールがフォーカスを取得するという問題に直面しています。

全体として、コンポーネントでできることは非常に素晴らしいですが、動作を制御/変更できるようにすることと、事前定義された動作のセットを組み合わせることで、気が狂います。

私の提案は、メッセージをサブクラス化して、コードを介してフォーカスコントロールを直接指示し、ウィンドウをバイパスしてそうしようとしないようにすることです。

5
Shawn E