InputBinding and WebBrowser control - c#

I have a very simple application where I'm trying to bind keyboard shortcuts to a WPF command that is bound to a menu item. The application itself consists of just a Menu and a WebBrowser control.
When I'm within the WebBrowser, the keyboard shortcuts are not routed up to the WPF Menu. For example, typing 'Ctrl+O' when focused in the web browser shows the IE open page. Additionally, in this application, unless I have the Menu focused (by typing in Alt) the input bindings don't fire. For example, I can't focus on the WPF window by clicking on the title bar and then type the shortcuts. The full code is replicated below:
MainWindow.xaml
<Window x:Class="TestInputBindingsOnMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="600" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu IsMainMenu="True" x:Name="_mainMenu" Grid.Row="0" />
<WebBrowser Source="http://google.com" Grid.Row="1" />
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace TestInputBindingsOnMenu
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Initialize();
}
private void Initialize()
{
MenuItem fileMenu = new MenuItem();
MenuItem fileNew = new MenuItem();
MenuItem fileOpen = new MenuItem();
MenuItem fileExit = new MenuItem();
fileMenu.Header = "File";
fileNew.Header = "New";
fileOpen.Header = "Open";
fileExit.Header = "Exit";
fileMenu.Items.Add(fileNew);
fileMenu.Items.Add(fileOpen);
fileMenu.Items.Add(fileExit);
_mainMenu.Items.Add(fileMenu);
var fileNewCommand = CreateCommand("New");
var fileOpenCommand = CreateCommand("Open");
var fileExitCommand = CreateCommand("Exit");
_mainMenu.CommandBindings.Add(new CommandBinding(fileNewCommand, ExecuteNew));
_mainMenu.CommandBindings.Add(new CommandBinding(fileOpenCommand, ExecuteOpen));
_mainMenu.CommandBindings.Add(new CommandBinding(fileExitCommand, ExecuteExit));
fileNew.Command = fileNewCommand;
fileOpen.Command = fileOpenCommand;
fileExit.Command = fileExitCommand;
_mainMenu.InputBindings.Add(new InputBinding(fileNewCommand, new KeyGesture(Key.N, ModifierKeys.Control)));
_mainMenu.InputBindings.Add(new InputBinding(fileOpenCommand, new KeyGesture(Key.O, ModifierKeys.Control)));
_mainMenu.InputBindings.Add(new InputBinding(fileExitCommand, new KeyGesture(Key.F4, ModifierKeys.Alt)));
}
private void ExecuteNew(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("New!!");
}
private void ExecuteOpen(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Open!!");
}
private void ExecuteExit(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Exit!!");
}
private static RoutedCommand CreateCommand(string label)
{
return new RoutedCommand(label, typeof(MainWindow));
}
}
}

Easy Solution
Add the input bindings to the WebBrowser control as well as the main menu:
Browser.InputBindings.Add(new KeyBinding(ApplicationCommands.Open,
new KeyGesture(Key.O, ModifierKeys.Control)));
Hard Solution
What is happening here is that the UIElement is using the KeyDown event, while you want to use PreviewKeyDown event, or add a handler that also handles Handled routed events. Look up Tunnelling and Bubbling if you don't know about this.
Since this is handled in the UIElement class, I would advise using a different pattern in this situation. The MVVM Light framework provides an EventToCommand behavior. If you can route the PreviewKeyDown event of the window to the right commands instead of using KeyBinding with the InputBindings collection of the UIElement you will have your solution.
You will need some custom code to check which key was pressed and to which command the routing should be.

Related

Why Drag&Drop with files doesn't work in a Window of an Avalonia application?

I'm trying to implement Drag & Drop with files on a ListBox which is contained in a Window of an Avalonia project.
As I couldn't get it working and I thought that ListBox perhaps is a special case, I tried to make a similar example like the one from ControlCatalogStandalone.
While the code in ControlCatalogStandalone works as expected, virtually the same code in my test application doesn't work properly.
The relevant code in ControlCatalogStandalone belongs to a UserControl, where in my application it belongs to the MainWindow. Could this be the cause for the misbehavior?
I created a new Avalonia MVVM Application based on the NuGet packages 0.9.11 in Visual Studio 2019.
I also tried version 0.10.0-preview2 in vain.
This is the XAML file:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:DragAndDropTests.ViewModels;assembly=DragAndDropTests"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Width="400" Height="200"
x:Class="DragAndDropTests.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="DragAndDropTests">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Drag+Drop</TextBlock>
<TextBlock Classes="h2">Example of Drag+Drop capabilities</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMe">
<TextBlock Name="DragState">Drag Me</TextBlock>
</Border>
<Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16"
DragDrop.AllowDrop="True">
<TextBlock Name="DropState">Drop some text or files here</TextBlock>
</Border>
</StackPanel>
</StackPanel>
</Window>
And this is the Code Behind:
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using System;
using System.Diagnostics;
namespace DragAndDropTests.Views
{
public class MainWindow : Window
{
private TextBlock _DropState;
private TextBlock _DragState;
private Border _DragMe;
private int DragCount = 0;
public MainWindow()
{
Debug.WriteLine("MainWindow");
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
_DragMe.PointerPressed += DoDrag;
AddHandler(DragDrop.DropEvent, Drop);
AddHandler(DragDrop.DragOverEvent, DragOver);
}
private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
{
Debug.WriteLine("DoDrag");
DataObject dragData = new DataObject();
dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
switch (result)
{
case DragDropEffects.Copy:
_DragState.Text = "The text was copied"; break;
case DragDropEffects.Link:
_DragState.Text = "The text was linked"; break;
case DragDropEffects.None:
_DragState.Text = "The drag operation was canceled"; break;
}
}
private void DragOver(object sender, DragEventArgs e)
{
Debug.WriteLine("DragOver");
// Only allow Copy or Link as Drop Operations.
e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
// Only allow if the dragged data contains text or filenames.
if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames))
e.DragEffects = DragDropEffects.None;
}
private void Drop(object sender, DragEventArgs e)
{
Debug.WriteLine("Drop");
if (e.Data.Contains(DataFormats.Text))
_DropState.Text = e.Data.GetText();
else if (e.Data.Contains(DataFormats.FileNames))
_DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
}
private void InitializeComponent()
{
Debug.WriteLine("InitializeComponent");
AvaloniaXamlLoader.Load(this);
_DropState = this.Find<TextBlock>("DropState");
_DragState = this.Find<TextBlock>("DragState");
_DragMe = this.Find<Border>("DragMe");
}
}
}
Drag & Drop within the application works well in ControlCatalogStandalone and in my application.
The succession of events is DoDrag, DragOver, DragOver, …, Drop in this case.
Dragging a file from Windows Explorer to the ControlCatalogStandalone works well.
The succession of events is DragOver, DragOver, …, Drop
Dragging a file from Windows Explorer to my application doesn't work.
None of the expected events is called here.
What's wrong with my test application?

WPF DataGrid: Show dynamic context menu on left click

From time to time I'd like to show a context menu when clicking on a Cell in a DataGrid.
I create the ContextMenu programmatically and then display it with ContextMenu.IsOpen=true. In the example below, it works when clicking inside the Grid panel, but it doesn't, wenn clicking on a cell(UIElement inside a cell) of the DataGrid.
That is the difference? What do I need to do to make it work on a DataGridCell as well?
Here comes a demo version, first XAML and below the code behind.
<Window x:Class="WpfApplication7_delete_me.MainWindow"
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:WpfApplication7_delete_me"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid MouseDown="Grid_MouseDown" Background="Beige">
<DataGrid x:Name="dataGrid" HorizontalAlignment="Left" VerticalAlignment="Top" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" MouseDown="TextBlock_MouseDown" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Here comes the code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication7_delete_me {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
Person p1 = new Person(); p1.Name = "abc";
Person p2 = new Person(); p2.Name = "1q23";
List<Person> l = new List<Person>() { p1, p2 };
dataGrid.ItemsSource = l;
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e) {
ContextMenu cm = new ContextMenu();
MenuItem mi = new MenuItem();
mi.Header = "hallo";
cm.Items.Add(mi);
cm.IsOpen = true;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e) {
ContextMenu cm = new ContextMenu();
MenuItem mi = new MenuItem();
mi.Header = "hallo";
cm.Items.Add(mi);
cm.IsOpen = true;
}
}
class Person {
public string Name { get; set; }
}
}
After some time I found two solutions:
1) It's working when using PreviewMouseDown instead of MouseDown.
2) Using: Dispatcher.BeginInvoke(new Action(() => { c.IsOpen = true; }), null);
But why is setting IsOpen inside MouseDown event not working?
About the first solution.
there are some issues while using the MouseDown event because the event might be marked as handled by other controls.
the PreviewMouseDown is a preview event and it is not marked, hence when you use it , it comes all the way down from the root element and controls to your implementation.
for more information you can read here:MSDN UIElement.MouseDown Event
Define an empty ContextMenu for the DataGrid.
<DataGrid.ContextMenu>
<ContextMenu x:Name="CtxMenu">
</ContextMenu>
</DataGrid.ContextMenu>
And handle ContextMenuOpening event :
private void DataGrid_ContextMenuOpening_1(object sender, ContextMenuEventArgs e)
{
ContextMenu ctxmenu = (sender as DataGrid).ContextMenu;
// suppress ContextMenu if empty
e.Handled = ctxmenu.Items.Count == 0;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
ContextMenu ctxmenu = Dgrd.ContextMenu;
MenuItem mi = new MenuItem();
mi.Header = "hallo";
ctxmenu.Items.Add(mi);
}
Better would be to handle PreviewMouseDown event at DataGrid level like this <DataGrid ... DataGridCell.PreviewMouseDown="DataGridCell_MouseDown" ... /> .
That way you get ContextMenu as ContextMenu ctxmenu = (sender as DataGrid).ContextMenu;. Also its good to use preview events if you want to do some initial preparation.

WPF: Popout existing usercontrol

Imagine next situation: I do have application window with several usercontrols inside. They was displayed side by side in past, but now I want to show one of them in popup window. Not in Popup control but new Window.
See example XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication3="clr-namespace:WpfApplication3"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<wpfApplication3:UserControl1 Visibility="Hidden"
x:Name="UserControl1"/>
<Button Click="ButtonBase_OnClick"
Width="100"
Height="60">open window</Button>
</Grid>
In code behind I need to deattach usercontrol from current Window and assign to new one:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var parent = VisualTreeHelper.GetParent(UserControl1);
if (parent != null)
{
parent.RemoveChild(UserControl1);
}
var w = new Window
{
Content = UserControl1,
Title = "sample",
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.CanResize
};
w.Show();
}
And after calling w.Show() I always getting blank white window.
If in button click handler change
Content = UserControl1
to
Content = new UserControl1()
I will get right content as well.
But I can't use this way because I want to keep my usercontrol state during pop-out and pop-in events.
So how can I show in new window existing usercontrol without recreating it?
I am not sure how you are calling RemoveChild on a DependencyObject as that method doesn't seem to exist. Note that VisualTreeHelper.GetParent returns a DependencyObject so, the code you posted should not compile unless you have an Extension method somewhere defining RemoveChild.
In your case what you want to do is cast your parent object to type Grid or Panel and then remove the UserControl from the Children property, then set your UserControl as the Content of your window.
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Grid parent = VisualTreeHelper.GetParent(UserControl1) as Grid;
if (parent != null)
{
parent.Children.Remove(UserControl1);
}
var w = new Window
{
Content = UserControl1,
Title = "sample",
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.CanResize
};
w.Show();
}
There was a similar question asked here
that gives a very detailed answer.
The quick answer is that you will have to remove the control from the main window and then add it to the popup window.

Pop context menu in textbox when # is input

I am trying to build a chat application, I would like to mimic facebook tag friends functionality. When the user types # in the textblock, I want to pop a context menu with a list of items. How can this be triggered in wpf mvvm app?
Example.
I would do it the following way :
Subscribe to the TextChanged event and whenever there is a change that contains # then show the popup, otherwise hide it.
Note that it tracks for new changes in the TextBox, therefore the popup will disappear as soon as the user presses another key or in your case when the user selected a user from the auto-completion you have provided in the pop-up.
User hasn't typed #
User just typed #
<Window x:Class="WpfApplication11.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">
<Grid>
<Popup x:Name="MyPopup" Placement="Center">
<Popup.Child>
<Border BorderBrush="Red" BorderThickness="1" Background="White">
<Grid>
<TextBlock>My popup</TextBlock>
</Grid>
</Border>
</Popup.Child>
</Popup>
<TextBox TextChanged="TextBoxBase_OnTextChanged" />
</Grid>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication11
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
{
var textBox = (TextBox) sender;
foreach (TextChange textChange in e.Changes)
{
string substring = textBox.Text.Substring(textChange.Offset, textChange.AddedLength);
if (substring == "#")
{
MyPopup.IsOpen = true;
}
else
{
MyPopup.IsOpen = false;
}
}
}
}
}
That said, you might want to further enhance it and integrate it properly your application ;-)

Dynamically preventing a TextBox from getting focus

I have a System.Windows.Controls.TextBox which I would like to behave as follows: When you click it, it is determined dynamically if the TextBox gets focus or not. Here's a toy application which contains a failed attempt at accomplishing this:
<!-- MainWindow.xaml -->
<Window x:Class="Focus.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Name="myLabel" Grid.Row="0" Background="Red"></Label>
<TextBox Name="myTextbox" Grid.Row="1" Background="Green"></TextBox>
</Grid>
</Window>
// MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace Focus
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myTextbox.PreviewGotKeyboardFocus += myTextbox_GotKeyboardFocus;
}
private static readonly Random myRandom = new Random();
private void myTextbox_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
int randomInt = myRandom.Next(0, 2); // 0 or 1
myLabel.Content = randomInt;
if(randomInt==0)
{
// PREVENT FOCUS - INSERT CODE HERE. (The line below is a failed attempt.)
FocusManager.SetFocusedElement(this, myLabel);
}
}
}
}
The following code seems to do the trick:
// PREVENT FOCUS - INSERT CODE HERE.
myLabel.Focusable = true;
myLabel.Focus();
myLabel.Focusable = false;
I also changed this line of code:
myTextbox.PreviewGotKeyboardFocus += myTextbox_GotKeyboardFocus;
into this:
myTextbox.GotFocus += myTextbox_GotKeyboardFocus;
I hope you know that you hooked up to the keyboard focus (TAB key).
void textBox1_GotFocus(object sender, System.EventArgs e)
{
if (!this.checkBox1.Checked)
this.checkBox1.Focus();
else
this.textBox1.Focus();
}

Categories

Resources