Virtual Win8 keyboard in WebBrowser control - c#

I'm currently doing a touch application in WPF. It works great so far but sometimes I need to launch the built-in web browser control. My problem is that despite that the zoom, scroll and such are working, I can't get the Windows 8 virtual keyboard (like in IE 11) to show up when the user gets the focus on web text inputs.
Is there any way to achieve such behavior ? Please keep in mind my WPF app is supposed to run topmost and entirely fullscreen all the time so I can't ask the user to bring up the virtual keyboard manually.

Finally found it here... As written, the Winforms WebBrowser has better wrapper for HTMLDocument, making it way easier than using MSHTML interop. Here's a code snippet :
C# :
public partial class BrowserWindow : Window
{
public BrowserWindow(string url)
{
InitializeComponent();
WebView.ScriptErrorsSuppressed = true;
WebView.AllowNavigation = true;
WebView.Navigate(new Uri(url));
WebView.DocumentCompleted += LoadCompleteEventHandler;
}
private void LoadCompleteEventHandler(object sender, WebBrowserDocumentCompletedEventArgs navigationEventArgs)
{
HtmlElementCollection elements = this.WebView.Document.GetElementsByTagName("input");
foreach (HtmlElement input in elements)
{
if (input.GetAttribute("type").ToLower() == "text")
{
input.GotFocus += (o, args) => VirtualKeyBoardHelper.AttachTabTip();
input.LostFocus += (o, args) => VirtualKeyBoardHelper.RemoveTabTip();
}
}
}
}
XAML :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
x:Class="PlayPlatform.BrowserWindow"
Title="Browser" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen" WindowState="Maximized" Topmost="True" ShowInTaskbar="False" WindowStyle="None" AllowDrop="False" AllowsTransparency="False">
<WindowsFormsHost HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<wf:WebBrowser x:Name="WebView" />
</WindowsFormsHost>
</Window>
The VirtualKeyBoardHelper methods are manuallly launching and terminating tabtip.exe.

Related

UWP: How do you make a Border a click through shield?

I have situation where I want an overlay control to block UI interactions on a Page for everything that is behind a border. I have tried setting Border.ManipulationMode to False. I have set IsTapEnabled, IsRightTapEnabled, IsDoubleTapEnabled, and IsHitTestVisible to False.
I also tried subscribing to the Tapped and PointerEntered events, and setting the args Handled property to true. After all of this I can still click on Buttons through the border, and invoke their commands. Below are a few screenshots for context:
Page with no overlay
Page now has what should be an overlay that blocks controls behind it
A button capturing PointerOver that shouldn't be
Here is the UserControl xaml that becomes the overaly on the Page:
<UserControl x:Class="PocMvvmToolkitApp.Dialogs.DialogShell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid x:Name="overlayGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!--dialogShield is the Border that I want to prevent click through on-->
<Border x:Name="dialogShield"
Background="#AAFFFFFF"
IsHitTestVisible="False"
ManipulationMode="None"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsDoubleTapEnabled="False"
IsHoldingEnabled="False"
IsRightTapEnabled="False"
IsTapEnabled="False"/>
<Border x:Name="dialogBorder"
BorderBrush="Black"
BorderThickness="1" />
</Grid>
Attempting to handle the events:
public DialogShell()
{
this.InitializeComponent();
this.allDialogs = new List<ExtendedContentDialog>();
this.visibleDialogs = new List<ExtendedContentDialog>();
////Doesn't work
this.dialogShield.PointerEntered += this.OnModalShieldPointerEntered;
this.dialogShield.Tapped += this.OnModalShieldTapped;
}
private void OnModalShieldTapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
////Doesn't block click through
e.Handled = true;
}
private void OnModalShieldPointerEntered(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
e.Handled = true;
}
On the Page.xaml.cs here is where I add or remove the DialogShell control to the parent Grid on the page:
private void OnDialogStackChanged(Args.DialogStackChangedEventArgs args)
{
switch (args.Context)
{
case Args.DialogStackChangedContext.Showing:
if (this.dialogShell == null)
{
this.dialogShell = new DialogShell();
this.dialogShell.ShowDialog(args.Dialog);
this.rootGrid.Children.Add(this.dialogShell);
Grid.SetColumnSpan(this.dialogShell, 2);
}
break;
case Args.DialogStackChangedContext.Closing:
if (this.dialogShell != null)
{
this.dialogShell.RemoveDialog(args.Dialog);
if (this.dialogShell.AllDialogs.Count == 0)
{
this.rootGrid.Children.Remove(this.dialogShell);
this.dialogShell = null;
}
}
break;
}
}
Any help with this Border situation would be appreciated. Before someone recommends using ContentDialog, please don't, I have my reasons for this setup. Thanks!

Use ChromiumFX/ChromiumWebBrowser inside an C# addin

I’m trying to add a ChromiumWebBrowser components inside a C#/WPF addin. (Before the addin used CefSharp but the new version of the host application run addin inside custom AppDomain so CefSharp doesn’t work anymore.)
To the problem is, when I call ChromiumWebBrowser.Initialize() a new instance of the host application is launch 🤔 (with some command line args which are dropped by the application).
I really don’t understand the stuff around this command line.
Can you explain to me where I do a mistake ? ChromiumWebBrowser cannot be used inside addin ? I miss somethings ?
Here the C# sample of my code
public partial class myWindow : Window
{
public myWindow()
{
CfxRuntime.LibCefDirPath = #"E:\sources\app\Output\Debug_msvc2017_x64\Addins\cef\Release64";
ChromiumWebBrowser.OnBeforeCfxInitialize += ChromiumWebBrowser_OnBeforeCfxInitialize;
ChromiumWebBrowser.OnBeforeCommandLineProcessing += ChromiumWebBrowser_OnBeforeCommandLineProcessing;
Chromium.WebBrowser.ChromiumWebBrowser.Initialize();
InitializeComponent();
// browser is a ChromiumWebBrowser instance
browser.MouseClick += Wb_MouseClick;
browser.Show();
}
private void Wb_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
{
(sender as ChromiumWebBrowser).LoadUrl("https://www.google.com"); // properly trigger but nothings will be displayed
}
static void ChromiumWebBrowser_OnBeforeCommandLineProcessing(CfxOnBeforeCommandLineProcessingEventArgs e)
{
Console.WriteLine(e.CommandLine.CommandLineString); // here the command line create a new instance of the application where the plugin is loaded
}
static void ChromiumWebBrowser_OnBeforeCfxInitialize(OnBeforeCfxInitializeEventArgs e)
{
e.Settings.LocalesDirPath = #"E:\sources\app\Output\Debug_msvc2017_x64\Addins\cef\locales";
e.Settings.ResourcesDirPath = #"E:\sources\app\Output\Debug_msvc2017_x64\Addins\cef\Resources";
}
}
‌
And the xaml part
‌
<Window x:Class="App.Views.myWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:App.Views"
xmlns:webbrowser="clr-namespace:Chromium.WebBrowser;assembly=ChromiumWebBrowser"
mc:Ignorable="d"
Title="myWindow" Height="800" Width="1200">
<Grid x:Name="grid1">
<WindowsFormsHost x:Name="wpfContainer">
<webbrowser:ChromiumWebBrowser x:Name="browser"></webbrowser:ChromiumWebBrowser>
</WindowsFormsHost>
</Grid>
</Window>
‌
Thanks in advance for your help ;)

FlowDocument and XamlReader x:Class

In my MainWindow I have a FlowDocumentScrollViewer binding its property Document to a FlowDocument in my MainViewModel.
This document is loaded from an external xaml file store on a remote computer. Currently I'm able to load this document properly via XamlReader.Load(xamlfile) and display it in the FlowDocumentScrollViewer. So far so good.
The problem occurs when I try to add hyperlink in this document. Because to handle the RequestNavigate event I need a x:Class. For the time being this Class need to be my MainWindow because the event is handle in the code-behind. Obviously when I add x:Class="Ugrader.MainWindow" in my external document I get a lovely 'System.Windows.Markup.XamlParseException' at the moment of parsing.
So is there a way to solve this ?
Here is piece of my code
MainWindow.xaml
<Window x:Class="Ugrader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Geco3-Upgrading version"
WindowStyle="none" ResizeMode="NoResize" ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
Height="400" Width="700"
DataContext="{Binding Main,Source={StaticResource Locator}}">
<FlowDocumentScrollViewer Grid.Column="1" Background="{x:Null}" VerticalScrollBarVisibility="Hidden"
Document="{Binding WhatsNewDoc}"/>
</Window>
MainViewModel.cs
namespace Ugrader.ViewModel
{
public class MainViewModel : ViewModelBase
{
#region Constructor
public MainViewModel()
{
try
{
FileStream xamlFile = new FileStream(updateLocation + "whatsnew.xaml", FileMode.Open, FileAccess.Read);
FlowDocument current = System.Windows.Markup.XamlReader.Load(xamlFile) as FlowDocument;
WhatsNewDoc = current;
}
catch (Exception)
{
}
}
#endregion
#region Properties
private FlowDocument _watsNewDoc = new FlowDocument();
public FlowDocument WhatsNewDoc
{
get
{
return _watsNewDoc;
}
set
{
if(_watsNewDoc != value)
{
_watsNewDoc = value;
RaisePropertyChanged("WhatsNewDoc");
}
}
}
#endregion
}
}
The external FlowDocument
<FlowDocument x:Class="Ugrader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ColumnWidth="400" FontSize="12" FontFamily="Century Gothic" Foreground="LightGray">
<Paragraph>
<Italic>
For additionnal information, please watch this
<Hyperlink TextDecorations="{x:Null}" RequestNavigate="Hyperlink_Clicked" NavigateUri="path_to_the_file" >video</Hyperlink>
</Italic>
</Paragraph>
</FlowDocument>
By the way, is there a way to handle this parse exception (in case of bad external file), because even in this try/catch block, this stop my program.
Thanks you in advance,
Bastien.
I find a way to deal with my problem, it's not really beautiful, do not respect the spirit of mvvm, but well, this is working.
So, as it's not possible to add x:Class at runtime (I guess), I came to the idea of handling the RequestNavigate event of each Hyperlinkat runtime. So the solution is pretty simple (and dirty).
In code-behind (yeah I know, it's ugly), on the MainWindow loaded event, I find all hyperlinks in my document, and handle each RequestNavigate events. As simple (and dirty) as this.
Here is some code :
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var hyperlinks = GetVisuals(this).OfType<Hyperlink>();
foreach (var link in hyperlinks)
link.RequestNavigate += link_RequestNavigate;
}
public static IEnumerable<DependencyObject> GetVisuals(DependencyObject root)
{
foreach (var child in LogicalTreeHelper.GetChildren(root).OfType<DependencyObject>())
{
yield return child;
foreach (var descendants in GetVisuals(child))
yield return descendants;
}
}
If someone has a better solution, I take it.

Waiting for Navigation Complete to continue

My team has been struggling for the Best Practices approach for handling the response from a Navigation for about 3 weeks now without a definitive answer. We have both a WPF and a Windows Phone 8 solution where we share a common code base.
For Phone 8, we display our company's splash screen and start initializing our data. Due to our complex nature, we have a very long list of steps to initialize before the application is fully operational.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode == NavigationMode.New)
{
BeginAppStartup();
return;
}
....
void BeginAppStartup()
{
// Initialization of settings and environment
At this point, we need to optionally display up to 5 different pages requesting additional data. So we check our commands, and if executable, then we navigate and optionally display a Communication Page, a Login Page, or several other possible pages.
if( condition )
DisplayLoginPage();
In WPF, this would be easy since we have modal dialogs and can wait for the user's input before continuing. But in the asynchronous world of WP8, we no longer have this.
To accommodate this platform, we have implemented a wide array of attempts, including saving the next command to execute. The only place that I believe that we are assured that the page is closed is in the OnNavigatedTo of the splash page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode == NavigationMode.Back)
{
// If we are returning to the splash from another set up page, check if there are new actions to perform
if (_startupAction != null)
{
_startupAction();
return;
}
Unfortunately, this is only marginally acceptable since the Login page doesn't close properly since all of our action is in the UI thread. The code continues, but the splash page is hidden behind the still visible Login page.
We have also tried out AutoResetEvents, but since we must Navigate out of the UI thread, we can't block the UI thread. We've also tried Task.Run with similar issues.
// Doesn't work.
void ShowLoginPage()
{
if (condition)
{
_manualResetEvent.Reset();
NavigationService.Navigate(new Uri("/Views/Login.xaml", UriKind.Relative)
_manualResetEvent.WaitOne();
}
}
We've also tried the async/await tasks, but we encounter similar problems. I believe that this is the best solution, but we're not having any better luck than previously.
So back to the question: What is the Best Practice for Navigating from a splash page, optionally to a login page, and then to await for the login page to close completely before continuing?
This sounds like a very common scenario, yet I'm baffled! Thanks for your answers.
It is not difficult to provide a functionality similar to a modal dialog. I'm not sure if it is a great UI design decision, but it certainly can be done. This MSDN blog post describes how to do it with UserControl as a custom adorner. It was written in 2007, by that time there was no async/await nor WP8.
I'm going to show how to do a similar thing using Popup control (which is present in both WPF and WP8) and async/await. Here's the functional part:
private async void OpenExecuted(object sender, ExecutedRoutedEventArgs e)
{
await ShowPopup(this.firstPopup);
await ShowPopup(this.secondPopup);
}
Each popup can and should be data-bound to the ViewModel.
C# (a WPF app):
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace Wpf_22297935
{
public partial class MainWindow : Window
{
// http://stackoverflow.com/q/22297935/1768303
public MainWindow()
{
InitializeComponent();
}
EventHandler ProcessClosePopup = delegate { };
private void CloseExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.ProcessClosePopup(this, EventArgs.Empty);
}
// show two popups with modal-like UI flow
private async void OpenExecuted(object sender, ExecutedRoutedEventArgs e)
{
await ShowPopup(this.firstPopup);
await ShowPopup(this.secondPopup);
}
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
// helpers
async Task ShowPopup(Popup popup)
{
var tcs = new TaskCompletionSource<bool>();
EventHandler handler = (s, e) => tcs.TrySetResult(true);
this.ProcessClosePopup += handler;
try
{
EnableControls(false);
popup.IsEnabled = true;
popup.IsOpen = true;
await tcs.Task;
}
finally
{
EnableControls(true);
popup.IsOpen = false;
popup.IsEnabled = false;
this.ProcessClosePopup -= handler;
}
}
void EnableControls(bool enable)
{
// assume the root is a Panel control
var rootPanel = (Panel)this.Content;
foreach (var item in rootPanel.Children.Cast<UIElement>())
item.IsEnabled = enable;
}
}
}
XAML:
<Window x:Class="Wpf_22297935.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" CanExecute="CanExecute" Executed="OpenExecuted" />
<CommandBinding Command="ApplicationCommands.Close" CanExecute="CanExecute" Executed="CloseExecuted"/>
</Window.CommandBindings>
<DockPanel>
<Border Padding="5">
<StackPanel>
<StackPanel>
<TextBlock>Main:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Open" HorizontalAlignment="Left" Width="50">Open</Button>
</StackPanel>
<Popup Name="firstPopup" AllowsTransparency="true" Placement="Center">
<Border Background="DarkCyan" Padding="5">
<StackPanel Background="DarkCyan" Width="200" Height="200" HorizontalAlignment="Left">
<TextBlock>First:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Close" HorizontalAlignment="Left" Width="50">Close</Button>
</StackPanel>
</Border>
</Popup>
<Popup Name="secondPopup" AllowsTransparency="true" Placement="Center">
<Border Background="DarkGray" Padding="5">
<StackPanel Background="DarkGray" Width="200" Height="200" HorizontalAlignment="Left">
<TextBlock>Second:</TextBlock>
<TextBox Height="20"></TextBox>
<Button Command="ApplicationCommands.Close" HorizontalAlignment="Left" Width="50">Close</Button>
</StackPanel>
</Border>
</Popup>
</StackPanel>
</Border>
</DockPanel>
</Window>
When dealing with such complex navigation, you should resort to creating your own navigation service. Instead of using NavigationService.Navigate, use your own wrapper over it.
In case of login page being after splash screen (and optionally), but before some other, you can always remove the page from backstack after navigation. So in this case you always navigate forward to another page and your custom service should remove last page if it is, say, a login page.

wpf popup doesn't close automatically when datagrid inside popup captures the mouse

I have a popup with StaysOpen=False so I want to close it by clicking anywhere outside of popup. Inside a popup I have a DataGrid. If I open popup and then click somewhere else the popup will be closed. But it won't happen if before clicking outside of popup I will click on column header in DataGrid. Test XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black">
<Grid>
<ToggleButton x:Name="btn" VerticalAlignment="Top">Open</ToggleButton>
<Popup StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=btn}" >
<DataGrid Width="150" Height="150">
<DataGrid.Columns>
<DataGridTextColumn Header="Column" />
</DataGrid.Columns>
</DataGrid>
</Popup>
</Grid>
</Window>
I think that it happens because column header captures the mouse on click and popup doesn't receive mouse events anymore. I've tried to add a handler on LostMouseCapture event in order to capture mouse back by popup but it doesn't seem to work that easy. Any ideas?
Maybe it will help.
Attached behavior:
public class DataGridColumnHeaderReleaseMouseCaptureBehavior {
public static DataGrid GetReleaseDGCHeaderBehavior(DependencyObject obj) {
return (DataGrid)obj.GetValue(ReleaseDGCHeaderBehaviorProperty);
}
public static void SetReleaseDGCHeaderBehavior(DependencyObject obj, Boolean value) {
obj.SetValue(ReleaseDGCHeaderBehaviorProperty, value);
}
public static readonly DependencyProperty ReleaseDGCHeaderBehaviorProperty =
DependencyProperty.RegisterAttached("ReleaseDGCHeaderBehavior",
typeof(DataGrid),
typeof(DataGridColumnHeaderReleaseMouseCaptureBehavior),
new UIPropertyMetadata(default(DataGrid), OnReleaseDGCHeaderBehaviorPropertyChanged));
private static Popup _popup;
private static void OnReleaseDGCHeaderBehaviorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var oldGrid = (DataGrid)e.OldValue;
if (oldGrid != null)
oldGrid.MouseLeave -= OnMouseLeave;
var refSender = d as Popup;
_popup = refSender;
if (refSender != null) {
var refGrid = e.NewValue as DataGrid;
if (refGrid != null) {
refGrid.MouseLeave += OnMouseLeave;
}
}
}
static void OnMouseLeave(object sender, MouseEventArgs args) {
if (_popup != null)
typeof(Popup).GetMethod("EstablishPopupCapture", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(_popup, null);
}
}
XAML:
<Popup x:Name="popup"
bhvrs:DataGridColumnHeaderReleaseMouseCaptureBehavior.ReleaseDGCHeaderBehavior="{Binding ElementName=dataGrid}">
<DataGrid x:Name="dataGrid"/>
</Popup>
I think you've stumbled onto just a plain old bug. I've reproduced this and could not find a reasonable way to get it working. I think you should file a bug with Microsoft. It seems like a component that captures the mouse and the uncaptures it doesn't restore the capture to the originally capturing component.
I had a similar problem recently altough not exactly the same, and it was in Silverlight. I hacked my way through it by searching the required control (in your case the popup I guess) with the GetTemplatedParent function, in the required event handler of the 'misbehaving' control, and programatically do what I wanted to do on it.
This is not a nice solution, and doesn't solve all the problems, but you can give it a try. Be sure you comment what have you done, because it can turn into a mess.
i had the same problem,and did something like this:
private void YourDataGrid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
YourDataGrid.CaptureMouse();
YourDataGrid.ReleaseMouseCapture();
}
but i'm looking for something better yet...

Categories

Resources