2013-08-15 64 views
25

Rõ ràng, có một số vấn đề về bàn phím và tập trung nghiêm trọng với WPF WebBrowser control. Tôi đã đặt cùng một ứng dụng WPF tầm thường, chỉ cần một WebBrowser và hai nút. Ứng dụng tải một đánh dấu HTML có thể chỉnh sửa rất cơ bản (<body contentEditable='true'>some text</body>) và thể hiện các điều sau:Bàn phím điều khiển WebBrowser và hành vi lấy nét

  • Tab đang hoạt động không đúng. Người dùng cần nhấn Tab hai lần để xem dấu mũ (con trỏ văn bản) bên trong WebBrowser và có thể nhập.

  • Khi người dùng chuyển từ ứng dụng (ví dụ: bằng Alt-Tab), sau đó quay lại, dấu mũ đã biến mất và cô ấy không thể nhập gì cả. Một cú click chuột vật lý vào khu vực cửa sổ của WebBrowser là cần thiết để lấy lại dấu mũ và tổ hợp phím.

  • Không nhất quán, hình chữ nhật tiêu điểm chấm chấm xuất hiện quanh WebBrowser (khi tabbing, nhưng không phải khi nhấp). Tôi không thể tìm cách để loại bỏ nó (FocusVisualStyle="{x:Null}" không giúp ích gì).

  • Bên trong, WebBrowser không bao giờ nhận được tiêu điểm. Đó là sự thật cho cả hai tập trung hợp lý (FocusManager) và tập trung đầu vào (Keyboard). Các sự kiện Keyboard.GotKeyboardFocusEventFocusManager.GotFocusEvent không bao giờ bị sa thải đối với WebBrowser (mặc dù cả hai đều thực hiện cho các nút trong cùng phạm vi tiêu điểm). Ngay cả khi dấu mũ nằm trong WebBrowser, FocusManager.GetFocusedElement(mainWindow) trỏ đến phần tử được lấy nét trước đó (một nút) và Keyboard.FocusedElementnull. Đồng thời, ((IKeyboardInputSink)this.webBrowser).HasFocusWithin() trả về true.

Tôi muốn nói, hành vi đó gần như quá rối loạn chức năng là đúng, nhưng đó là cách hoạt động của nó. Tôi có thể có thể đến với một số hacks để sửa chữa nó và mang nó liên tiếp với các điều khiển WPF bản địa như TextBox. Tôi vẫn hy vọng, có lẽ tôi đang thiếu một cái gì đó mơ hồ nhưng đơn giản ở đây. Có ai xử lý một vấn đề tương tự? Bất kỳ đề xuất về cách sửa lỗi này sẽ được đánh giá cao.

Tại thời điểm này, tôi có khuynh hướng phát triển trình bao bọc WPF trong nhà cho Điều khiển WebBrowser ActiveX, dựa trên HwndHost. Chúng tôi cũng là considering other alternatives tới WebBrowser, chẳng hạn như Chromium Embedded Framework (CEF).

Dự án VS2012 có thể được tải xuống từ here trong trường hợp ai đó muốn chơi với nó.

Đây là 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> 

Đây là mã C#, nó có một loạt các dấu vết chẩn đoán để chứng tỏ sự kiện tiêu điểm/bàn phím được định tuyến và nơi tập trung là:

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] Tình huống không tốt hơn nếu tôi lưu trữ WinForms WebBrowser (thay cho, hoặc cạnh nhau với 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> 

Sự cải thiện duy nhất là tôi thấy tập trung vào các sự kiện WindowsFormsHost.

[UPDATE] Một trường hợp cực đoan: hai điều khiển WebBrowser với hai dấu mũ thể hiện cùng một lúc:

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

này cũng cho thấy việc xử lý vấn đề trọng tâm là không cụ thể cho contentEditable=true nội dung.

+3

Cả WPF lẫn HTML đều đổ lỗi ở đây. Cả điều khiển WebBrowser của WPF đều đủ trưởng thành để xử lý tập trung đúng cách, cũng như không thể chỉnh sửa nội dung của HTML là tập trung thân thiện. Tôi đã sửa các vấn đề tiêu điểm tương tự với WebBrowser của WPF bằng cách thêm

vào bên trong , đặt nội dung bên trong và sau đó đặt tiêu điểm theo cách thủ công bằng cách sử dụng 'document.forms [0] .elements [0] .focus();'. Cũng liên quan đến việc duy trì vị trí dấu mũ với contentEditable, xem [post] này (http://stackoverflow.com/questions/16194364/maintain-cursor-position-in-contenteditable-div). – digitguy

+0

@digitguy, cảm ơn bạn. Tôi hiện đang chơi với một giải pháp tương tự: gói WebBrowser với UserControl, gọi InvokeScript ("focus") theo cách thủ công và xử lý các cửa sổ HTML bên trong sự kiện onfocus/onblur để đặt WPF tập trung vào UserControl cho phù hợp. Sẽ đăng ở đây những gì tôi sẽ đưa ra. Có vẻ như vấn đề caret không cụ thể đối với 'contentEditable = true', điều tương tự cũng áp dụng cho' textarea' hoặc 'input type = text'. – Noseratio

+0

Tôi muốn liên kết [this] (http://stackoverflow.com/questions/18437898/why-is-keyboard-focusedelement-null-when-focus-is-inside-windowsformshost-it-br) và [điều này ] (http://stackoverflow.com/questions/18417648/inconsistency-in-wpf-command-routing-behavior-depending-on-the-ui-focus-state) các câu hỏi ở đây như là một phần của nghiên cứu của tôi về chủ đề này. – Noseratio

Trả lời

5

Lý do nó hoạt động theo cách này có liên quan đến thực tế là nó là một điều khiển ActiveX mà chính nó là một lớp cửa sổ đầy đủ (nó xử lý tương tác chuột và bàn phím). Trong thực tế phần lớn thời gian bạn nhìn thấy các thành phần được sử dụng bạn sẽ tìm thấy nó là thành phần chính chiếm một cửa sổ đầy đủ vì điều này. Nó không phải được thực hiện theo cách đó nhưng nó trình bày các vấn đề.

Dưới đây là một diễn đàn thảo luận về các vấn đề chính xác cùng và nguyên nhân của nó có thể được làm rõ bằng cách đọc các bình luận cuối cùng liên kết bài viết:

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

Để phác thảo những vấn đề bạn đang gặp

  • Tabbing là không đúng. Người dùng cần nhấn Tab hai lần để xem dấu mũ (con trỏ văn bản) bên trong WebBrowser và có thể nhập.

    đó là do chính trình điều khiển trình duyệt là cửa sổ có thể được gắn thẻ. Nó không "chuyển tiếp" tab cho các phần tử con của nó ngay lập tức.

    Một cách để thay đổi điều này là xử lý thông báo WM cho chính thành phần đó nhưng hãy nhớ rằng khi bạn muốn tài liệu "con" bên trong nó có thể xử lý thư.

    Xem: Prevent WebBrowser control from stealing focus? cụ thể là "câu trả lời". Mặc dù câu trả lời của họ không có nghĩa là bạn có thể kiểm soát liệu thành phần có tương tác thông qua hộp thoại với người dùng hay không bằng cách đặt thuộc tính Im lặng (có thể hoặc không tồn tại trong điều khiển WPF ... không chắc chắn)

  • Khi người dùng tắt từ ứng dụng (ví dụ: với Alt-Tab), sau đó quay trở lại, dấu mũ đã biến mất và cô ấy không thể nhập gì cả. Một cú click chuột vật lý vào khu vực cửa sổ của WebBrowser là cần thiết để lấy lại dấu mũ và tổ hợp phím. Điều này là do bản thân kiểm soát đã nhận được tiêu điểm. Một xem xét khác là thêm mã để xử lý sự kiện GotFocus và sau đó "thay đổi", nơi lấy tiêu điểm. Phần khó khăn là tìm ra nếu điều này là "từ" tài liệu -> điều khiển trình duyệt hoặc ứng dụng của bạn -> kiểm soát trình duyệt. Tôi có thể nghĩ ra một vài cách hacky để làm điều này (tham khảo biến dựa trên mất sự kiện tập trung kiểm tra trên gotfocus ví dụ) nhưng không có gì mà hét thanh lịch.

  • Không nhất quán, hình chữ nhật tiêu điểm chấm chấm xuất hiện quanh WebBrowser (khi tabbing, nhưng không phải khi nhấp). Tôi không thể tìm ra cách để loại bỏ nó (FocusVisualStyle = "{x: Null}" không giúp được). Tôi tự hỏi nếu thay đổi Focusable có thể giúp đỡ hay cản trở. Không bao giờ thử nó nhưng tôi sẽ mạo hiểm một đoán rằng nếu nó đã làm việc nó sẽ ngăn chặn nó từ bàn phím điều hướng ở tất cả.

  • Bên trong, WebBrowser không bao giờ nhận được tiêu điểm. Đó là sự thật cho cả hai tập trung hợp lý (FocusManager) và tập trung đầu vào (Bàn phím). Các sự kiện Keyboard.GotKeyboardFocusEvent và FocusManager.GotFocusEvent không bao giờ bị sa thải cho WebBrowser (mặc dù cả hai đều làm cho các nút trong cùng phạm vi lấy nét). Ngay cả khi dấu mũ bên trong WebBrowser, FocusManager.GetFocusedElement (mainWindow) trỏ đến một phần tử được tập trung trước đó (một nút) và Keyboard.FocusedElement là null. Đồng thời, ((IKeyboardInputSink) này.webBrowser) .HasFocusWithin() trả về giá trị true. Mọi người gặp vấn đề với 2 điều khiển trình duyệt hiển thị tiêu điểm (cũng ... dấu mũ) hoặc thậm chí có điều khiển ẩn lấy tiêu điểm.

Tất cả trong tất cả nó khá tuyệt vời những gì bạn có thể làm với các thành phần nhưng nó chỉ là sự pha trộn của phép bạn kiểm soát/thay đổi hành vi cùng với bộ được xác định trước của hành vi là điên.

Đề xuất của tôi là cố gắng phân lớp tin nhắn để bạn có thể điều khiển lấy nét trực tiếp thông qua mã và bỏ qua cửa sổ của nó khi cố gắng làm như vậy.

+0

Shawn, cảm ơn bạn suy nghĩ. Chúng tôi đã xem xét việc sử dụng "móc" tuyến đường bằng cách sử dụng [CBT móc] (http://goo.gl/6OWPUm), nhưng quyết định ở lại với một "mềm" hack như mô tả trong các ý kiến ​​ở trên. Một phần của vấn đề là mã máy chủ WPF ActiveX không gọi chính xác 'IOleInPlaceActiveObject :: OnFrameWindowActivate'. Một số vấn đề trọng tâm khác [không cụ thể] (http://goo.gl/lRTQAx) đối với WB và tồn tại thực sự do sử dụng cửa sổ con. Tôi hy vọng, tại một thời điểm nào đó Microsoft sẽ trình duyệt giao diện người dùng hiện đại của họ như là một điều khiển WPF thuần túy. – Noseratio

3

Đối với bất kỳ ai gặp trở ngại khi đăng bài này và cần đặt bàn phím tập trung vào điều khiển trình duyệt (không phải là yếu tố cụ thể trong kiểm soát).

Trước tiên, hãy thêm tham chiếu dự án (trong Tiện ích mở rộng trong VS) cho Microsoft.mshtml.

Tiếp theo, bất cứ khi nào bạn muốn tập trung sự kiểm soát trình duyệt (nói ví dụ, khi tải Window), đơn giản là "trọng tâm" tài liệu HTML:

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

này sẽ đặt trọng tâm bàn phím bên trong web kiểm soát trình duyệt và bên trong cửa sổ ActiveX "ẩn", cho phép các phím như PgUp/PgDown hoạt động trên trang HTML.

Nếu bạn muốn, bạn có thể sử dụng lựa chọn DOM để tìm một yếu tố cụ thể trên trang và cố gắng focus() yếu tố cụ thể đó. Tôi đã không cố gắng điều này bản thân mình.

+0

BẠN ĐÃ THAM GIA CUỘC SỐNG CỦA TÔI. –