WebBrowser control keyboard and focus behavior - wpf

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.

For anyone else stumbling upon this post and needing to set keyboard focus to the browser control (not a particular element within the control, necessarily), this bit of code worked for me.
First, add a project reference (under Extensions in VS) for Microsoft.mshtml.
Next, whenever you'd like to focus the browser control (say for example, when the Window loads), simply "focus" the HTML document:
// Constructor
public MyWindow()
{
Loaded += (_, __) =>
{
((HTMLDocument) Browser.Document).focus();
};
}
This will place keyboard focus inside the web browser control, and inside the "invisible" ActiveX window, allowing keys like PgUp / PgDown to work on the HTML page.
If you want to, you might be able to use DOM selection to find a particular element on the page, and try to focus() that particular element. I have not tried this myself.

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.

Related

WPF take screenshot of application including hidden content

Under the category "limitations of the technology":
I have received the requirement to have a screenshot button in my application that will take a screenshot and launch a printer dialog. Fair enough. My code achieves that. I simply take my window, and use a RenderTargetBitmap to render the window.
However, the requirement now states that it should include all content that is hidden behind scrollbars. Meaning, that in the screenshot the application should look "stretched" in order to eliminate scrollbars, and show all data. For instance in case there is a large list or datagrid, all the data should be visible.
Keeping in mind that WPF might be virtualizing and not rendering things that are not in view, is there any way I can achieve this requirement? Is there a possibility of rendering the visual tree to a seperate infinite space and taking a screenshot there? Something else?
In response to comments:
The screenshot button is on an outer shell that only holds the menu. Inside this shell any of 800+ views can be hosted. These views could contain datagrids, lists, large forms consisting of textboxes... anything. There is no way to tell what is 'inside' without walking the visual tree.
The functionality requested is similar to printing a webpage in your browser to PDF. It will also give you the entire DOM instead of just what you see in the limited view of the browser.
XAML:
<Grid>
<Button
x:Name="btnPrint"
Width="50"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="BtnPrint_Click"
Content="Print" />
<ScrollViewer Height="500" HorizontalAlignment="Center">
<Grid x:Name="toPrint">
<!--your code goes here-->
</Grid>
</ScrollViewer>
</Grid>
C#:
private void BtnPrint_Click(object sender, RoutedEventArgs e)
{
var pdialog = new PrintDialog();
if (pdialog.ShowDialog() == true)
{
System.Windows.Size pageSize = new System.Windows.Size { Height = pdialog.PrintableAreaHeight, Width = pdialog.PrintableAreaWidth };
toPrint.Measure(pageSize);
toPrint.UpdateLayout();
pdialog.PrintVisual(toPrint, "Print");
}
}

Popup not closing on touch

I have a WPF popup which works perfectly using a mouse.
The popup will close when the user clicks outside the popup as expected - all is good so far
When on a touch screen the popup refuses to close until another control gains focus (for example by clicking a button or focusing a textbox)
What do I need to do to get the popup to close on touch like it does on click?
I assume it has something to do with not losing focus on touch but I cant find anything on google relating to this
Thanks
I have just launched this code:
<Window
x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Click="ButtonBase_OnClick" Width="50" Height="40" HorizontalAlignment="Left">Show</Button>
<Button Width="50" Height="40" HorizontalAlignment="Right">None</Button>
<Popup x:Name="MyPopup" Width="200" Height="100" IsOpen="True" StaysOpen="False" >
<Border Background="Red" Width="50" Height="50" ></Border>
</Popup>
</Grid>
</Window>
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MyPopup.IsOpen = true;
}
and it works properly even if I use touch device (on currently newest Windows version). You could have some other issues in your code, but to answer your question:
I am merely asking whether WPF focus changes happen differently between touch and mouse inputs
No, it does not. Please check my simple code or check if your application works ok on other touch device. Maybe you are handling touch events somewhere by your self?
I have to confess that I have not tested the solution proposed by Jonathan Dragon.
I've come to this question while searching for a solution to a similar issue (perhaps). To be able to show tooltips on some items in a touch interface we listen for the MouseUp and TouchUp events of the item, and in that case shows a Popup containing the tooltip information.
We use an attached property to set up the tooltip and the popup is reused with different content, for all items using this feature.
I experience that if I open the Popup and then close it by clicking a blank space - things work as expected.
If I open the Popup and then click a Button (something with a Command), the popup does disappear, but all the buttons do not respond until I open the Popup again and close it by clicking a blank space.
I was just reading up on the "StaysOpen" property of Popup, and well...
false if the System.Windows.Controls.Primitives.Popup control closes when a mouse or keyboard event occurs outside the System.Windows.Controls.Primitives.Popup control. The default is true.
It does not mention touch events specifically, while keyboard and mouse events are.
This COULD indicate that the popup might not react the same for touch events.

"Modal Dialog" in WPF - make overlay block key events

I'm creating a WPF application containing a "Main-Content" - Layer containing a TabControl and a "Dialog" - Layer containing an ItemsControl.
The XAML looks like this:
<Grid>
<TabControl>
..Some Tabs
</TabControl>
<ItemsControl>
<ContentControl Content={Binding Dialog1Property} />
<ContentControl Content={Binding Dialog2Property} />
</ItemsControl>
</Grid>
Usually "Dialog1Property" and "Dialog2Property" are null which means the ItemsControl is invisible. Whenever I assign a Control to one of them, it is shown in front of the TabControl which is exactly what I want. If I assign a gray Rectangle with an opacity of 0.7 to one of the Dialog - Properties it creates a Gray overlay.
If I click on the Tab, which is slightly visible through the overlay, nothing happens - the Rectangle blocks Mouse Events. It is, however, still possible to focus the TabControl behind the overlay using the Tab-Key and therefore it is also possible to switch tabs even though a Dialog is shown.
Is there an easy way to tell the rectangle to somehow block key events as it allready does with Mouseclicks?
Regards
BBRain
Yes, on your Rectangle, subscribe to the event PreviewKeyDown.
<Rectangle Opacity="0.7" Fill="Green" PreviewKeyDown="Rectangle_PreviewKeyDown" />
In its handler, simply set e.Handled = true;
private void Rectangle_PreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
}
Since routed events prefixed with "Preview..." are tunneling, the elements under your rectangle won't recieve the input.

Silverlight simulate mouse click by X, Y?

If there is a way to send mouse click event by location programatically it would be great, but if theres another approach that can solve following problem this it is fine too.
In my situation I got a canvas taking up whole application size (covering it completely) and when user clicks it with mouse I want to hide it, and then pass through this mouse click (taking its location x & y from user) to anything that is under canvas (in my case canvas visibility goes to collapsed so controls under it can be seen now).
I am guessing it is impossible, cause certain features like run silverlight fullscreen can only be done in button click handler (correct me if im wrong here).
But is there a place where I can read about those security based limitations of silverlight UI ?
you have to add an click event handler to your canvas. In this handler you get the x and y positon of your click (via MouseButtonEventArgs) and then you can use the VisualTreeHelper to get your "hit elements".
Lets assume the following xaml:
<Grid x:Name="LayoutRoot" Background="White">
<Button Width="50" Height="50" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<TextBox Text="MyText" Width="200" Height="100" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Canvas Background="Red" x:Name="MyCanvas" />
</Grid>
with the following code behind:
public MainPage()
{
InitializeComponent();
MyCanvas.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(handler), true);
}
void handler(object sender, MouseButtonEventArgs e)
{
var point = new Point(e.GetPosition(this).X, e.GetPosition(this).Y);
var elements = VisualTreeHelper.FindElementsInHostCoordinates(point, this);
foreach (var uiElement in elements)
{
if (uiElement is TextBox){
((TextBox) uiElement).Focus();
break;
}
if(uiElement is Button)
{
//do button stuff here
break;
}
}
MyCanvas.Visibility = Visibility.Collapsed;
MyCanvas.RemoveHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(handler));
}
But: In this simple example, you get at about 20 hit elements. But they are sorted in the correct "z-Index". So you can iterate through it and the first interesting element for you is where you could break(Maybe you can do this with LINQ, too). So for me, I know that the first hit TextBox is what I want to focus.
Is this what you need?
BR,
TJ

How to display a busy message over a wpf screen

Hey,
I have a WPF application based on Prism4. When performing slow operations, I want to show a busy screen. I will have a large number of screens, so I'm trying to build a single solution into the framework rather than adding the busy indicator to each screen.
These long running operations run in a background thread. This allows the UI to be updated (good) but does not stop the user from using the UI (bad). What I'd like to do is overlay a control with a spinning dial sort of thing and have that control cover the entire screen (the old HTML trick with DIVs). When the app is busy, the control would display thus block any further interaction as well as showing the spinny thing.
To set this up, I thought I could just have my app screen in a canvas along with the spinny thing (with a greater ZIndex) then just make the spinny thing visible as required.
This, however, is getting hard. Canvases do not seem well set up for this and I think I might be barking up the wrong tree.
I would appreciate any help. Thanks.
I have done this with a few programs. Here it is in a nutshell:
(This is easiest with MVVM. It has been so long since I used the codebehid for things like this I can't really say if there is a good way to do it.)
Create a border on your Main Window. I usually make it black with a 50% transparency. Add a grid to it, and put whatever you want inside to tell users it is busy. Size the border and the controls inside it to fill the screen.
Create a property on your main ViewModel for IsBusy as boolean. Initialize it as False. Bind the Visibility property of the Busy Border to that property.
Next, make a converter class for Busy(Boolean) to Visibility. Write the logic into that so that when value is True, Then visibility is Visible, when value is false, visibility is collapsed. ( http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx ).
Back on the border, add your converter to the binding. Add code to the ViewModel for each of your Pages or Views that calls back to that property and sets it to true when your other thread is busy.
Cory
Edit:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Grid>
<Border BorderBrush="Black" BorderThickness="1" Background="#80000000" Visibility="Collapsed">
<Grid>
<TextBlock Margin="0" TextWrapping="Wrap" Text="Busy...Please Wait" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="26.667" FontWeight="Bold" Foreground="#7EFFFFFF"/>
</Grid>
</Border>
<DockPanel x:Name="LayoutRoot">
<CheckBox Content="CheckBox" VerticalAlignment="Top"/>
<TextBlock TextWrapping="Wrap"><Run Text="TextBlock"/></TextBlock>
<UserControl x:Name="ViewViewView"/>
</DockPanel>
</Grid>
Look at this WPF toolkit with a busy indicator: https://github.com/xceedsoftware/wpftoolkit/wiki/BusyIndicator
I do this by simply displaying a dialog (so the user cannot interact with anything else and it will be displayed on top) then handle the Closing event (as the user could press Alt-F4) to see if the operation has finished otherwise I cancel the closing event:
myWaitWindow.ShowDialog(); // this can be borderless window with a spinny thing
void Window_Closing(object sender, CancelEventArgs e)
{
if(this.myOperation.IsRunning) // you would have to have some way to see when your operation has finished
e.Cancel = true;
}

Resources