Prevent KeyDown event bubbling from TextBox to MainWindow in WPF - c#

I have a program that contains a textbox control. A user is able to enter text into this control. The user can also press certain keys to trigger other actions (this is handled on the MainWindow). I have sample XAML and C# code to demonstrate my setup.
XAML
<Window x:Class="RoutedEventBubbling.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 />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" />
<TextBox x:Name="Output" Grid.Row="1" IsReadOnly="True" />
</Grid>
</Window>
C#
using System.Windows;
using System.Windows.Input;
namespace RoutedEventBubbling
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private int mHitCount = 0;
public MainWindow()
{
InitializeComponent();
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
this.Output.Text = string.Format("Hit Count: {0}", ++mHitCount);
}
}
}
As you may have realised, in the case of this program, if I start typing into the first TextBox the second will be updated with the hit count. This is an undesirable result as I might have certain actions that I wish to trigger when handling the MainWindow's OnKeyDown method.
As such, my question is this: is it possible to prevent the OnKeyDown method being called on the MainWindow, while still allowing text to be entered into the text box? I know of the e.Handled = true approach, but in this case doing that on the KeyDown event of the TextBox will prevent the text from being entered. If this is not possible, I will have to find some other way of handling it all.
Thanks in advance.
EDIT
I have just found a moderately hacky way around this issue. If I handle the MainWindow's OnTextInput method instead, then the result I desired will be achieved as the TextInput event of the TextBox will have been handled. Below is a sample of the code I used:
private Key mPressedKey;
protected override void OnKeyDown(KeyEventArgs e)
{
// Note: This method will be called first.
base.OnKeyDown(e);
// Store the pressed key
mPressedKey = e.Key;
}
protected override void OnTextInput(TextCompositionEventArgs e)
{
// Note: This method will be called second, and only if the textbox hasn't handled it.
base.OnTextInput(e);
this.Output.Text = string.Format("Hit Count: {0}", ++mHitCount);
// ... Handle pressed key ...
}

You could put a type-based guard on that statement:
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.OriginalSource is TextBoxBase == false)
{
mPressedKey = e.Key;
}
}
Quite possibly not the best solution either though.

I ended up working around this issue using the following setup:
private Key mPressedKey;
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
mPressedKey = e.Key;
}
protected override void OnTextInput(TextCompositionEventArgs e)
{
base.OnTextInput(e);
// ... Handle mPressedKey here ...
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
// Force focus back to main window
FocusManager.SetFocusedElement(this, this);
}
This was done in the MainWindow.xaml.cs. It's a bit of a hack, but it achieved the result I wanted.

Related

Detect global click UWP

I have a visible grid that has to collapse when I click outside of it. I solved it for half.
MainPage.xaml code:
<StackPanel Width="400" VerticalAlignment="Center">
<Button x:Name="btnOne" Content="Button One" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="30"/>
<Button x:Name="btnShowGrid" Content="Show Grid" Click="btnShowGrid_Click" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="30"/>
<Grid x:Name="ControlGrid" PointerEntered="ControlGrid_PointerEntered" PointerExited="ControlGrid_PointerExited" HorizontalAlignment="Center" Visibility="Visible" Background="LightGreen" Height="300" VerticalAlignment="Top" Width="300" Margin="30"/>
</StackPanel>
MainPage.xaml.cs code:
bool PointerInGrid = false;
public MainPage()
{
this.InitializeComponent();
}
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
base.OnPointerPressed(e);
if (!PointerInGrid)
{
ControlGrid.Visibility = Visibility.Collapsed;
}
}
private void btnShowGrid_Click(object sender, RoutedEventArgs e)
{
ControlGrid.Visibility = Visibility.Visible;
}
private void ControlGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
PointerInGrid = true;
}
private void ControlGrid_PointerExited(object sender, PointerRoutedEventArgs e)
{
PointerInGrid = false;
}
If I click into the Grid this remains visible, if I click out the grid goes to collapsed, so far everything is fine but, if I click the btnOne the grid remains visible.
So is possible detect a global click to collapse the grid?
As always thanks in advance.
Code that works in windows form:
const int WM_PARENTNOTIFY = 0x0210;
const int WM_LBUTTONDOWN = 0x0201;
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if (!DesignMode)
{
if (m.Msg == WM_PARENTNOTIFY)
{
if (m.WParam.ToInt32() == WM_LBUTTONDOWN)
{
MessageBox.Show("Clicked!");
}
}
}
base.WndProc(ref m);
}
The PointerPressed event is swallowed by the button so it cannot reach the underlying parent, where the event handler OnPointerPressed is executed.
There is a technique to pass the event down to the parent, please refer to this answer.
You can do it like this, in the page's constructor
public MainPage()
{
this.InitializeComponent();
//...other code
//then add this line
this.btnOne.AddHandler(UIElement.PointerPressedEvent,
new PointerEventHandler((s, e) => { e.Handled = false; }), true);
}
A second thought: Maybe you can just handle btnOne’s PointerPressed event, and in the handler set its event argument’s Handled property as false. Don’t need to use AddHandler, I mean. Give it a try.
As far as I known, the UWP is sandboxed, we can't use WndProc method to receive Window event Messages.
Specific Windows Runtime controls may have class-based handling for the PointerPressed input event. If so, the control probably has an override for the method OnPointerPressed. Typically the event is marked handled by the class handler, and the PointerPressed event is not raised for handling by any user code handlers on that control. For example, ButtonBase has class handling that handles PointerPressed and instead fires Click.
For more info, please refer PointerPressed.
When we click the Button, the OnPointerPressed event will not be fired.
If you want to your Button can fire OnPointerPressed event, you can create a class that inherited the Button class. In the class, you can override the OnPointerPresse event.
For example:
class MyButton: Button
{
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
}
}
By the way, if we override OnPointerPressed event in class that inherited the Button class, the Click event will not be fired.

WPF Drag&Drop vs manipulation

I want to enable Drag'n'Drop of a child control inside a parent that has IsManipulationEnabled = true.
When manipulation is enabled, touch events don't get promoted to mouse events. In order to enable promotion, one should handle touch events before manipulation logic steps in (see example). I've tried that and it works... until I call DoDragDrop for the first time. Then I no longer receive mouse events. Why?
Here's a minimal code to reproduce the issue. All drag and drop handling was removed for the sake of readability.
XAML:
<Window x:Class="Test.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" IsManipulationEnabled="True">
<Grid>
<Border Background="Red"
x:Name="Border"
TouchDown="Border_OnTouchDown"
MouseDown="Border_OnMouseDown"
TouchUp="Border_OnTouchUp"
Width="100" Height="50" />
</Grid>
</Window>
C#:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Border_OnTouchDown(object sender, TouchEventArgs e)
{
Debug.WriteLine("Border_OnTouchDown");
e.Handled = true;
e.TouchDevice.Capture((FrameworkElement)sender);
}
private void Border_OnMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Border_OnMouseDown!");
DragDrop.DoDragDrop((DependencyObject)sender, "", DragDropEffects.All);
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
Debug.WriteLine("OnManipulationStarted");
base.OnManipulationStarted(e);
}
private void Border_OnTouchUp(object sender, TouchEventArgs e)
{
((FrameworkElement)sender).ReleaseTouchCapture(e.TouchDevice);
e.Handled = true;
}
}
Output:
Border_OnTouchDown
Border_OnMouseDown! <- works first time
Border_OnTouchDown
Border_OnTouchDown <- no longer works, no matter how many times I tap
Border_OnTouchDown
Border_OnTouchDown
Border_OnTouchDown
...
If I don't call DoDragDrop in MouseDown - events get promoted as they should.
Looks like this is a bug in .NET. I had v4.5.2 installed. Now I've installed v4.6 and the problem is no longer reproducible.
I didn't even have to retarget the project to v4.6 or recompile it: just installing the new runtime fixed everything.
This solution works for any framework version:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Border_OnTouchDown(object sender, TouchEventArgs e)
{
Debug.WriteLine("Border_OnTouchDown");
IsManipulationEnabled = false;
e.TouchDevice.Capture(Border);
}
private void Border_OnMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Border_OnMouseDown!");
DragDrop.DoDragDrop((DependencyObject)sender, "", DragDropEffects.All);
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
Debug.WriteLine("OnManipulationStarted");
base.OnManipulationStarted(e);
}
private void Border_OnTouchUp(object sender, TouchEventArgs e)
{
Border.ReleaseTouchCapture(e.TouchDevice);
IsManipulationEnabled = true;
}
}
Here we are basically disabling manipulation if the user touched the Border. Since drag&drop operation could (and probably would) end outside of the Border, we also need to capture the touch input to be sure to receive the TouchUp event to re-enable manipulation.

How to allow key entry in a textbox while capturing it in a window?

I have a WPF window which captures both the KeyDown and KeyUp events. However, there is a TextBox in this window, and if a user is typing in that textbox, I want the general capture to effectively be ignored. The trouble I'm running into is that my efforts to ignore the general capture when typing in that textbox result in the user being unable to type in that textbox at all.
MainWindow.xaml
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="600" Width="500"
KeyDown="MainWindow_OnKeyDown"
KeyUp="MainWindow_OnKeyUp">
<StackPanel>
<TextBox x:Name="TextBoxAllowed" KeyDown="TextBoxAllowed_OnKeyDown" KeyUp="TextBoxAllowed_OnKeyUp" />
</StackPanel>
</Window>
MainWindow.xaml.cs
namespace MyProject
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MainWindow_OnKeyDown(object sender, KeyEventArgs e)
{
// take action using the value of e.Key
// should not be triggered when TextBoxAllowed has focus
// but should be triggered otherwise
}
private void MainWindow_OnKeyUp(object sender, KeyEventArgs e)
{
// take action using the value of e.Key
// should not be triggered when TextBoxAllowed has focus
// but should be triggered otherwise
}
private void TextBoxAllowed_OnKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
}
private void TextBoxAllowed_OnKeyUp(object sender, KeyEventArgs e)
{
e.Handled = true;
}
}
}
I'd like it so that when TextBoxAllowed has focus, the user can type in it normally, and that the MainWindow_OnKey* event handlers are not triggered. But when that field doesn't have focus, if any typing takes place, then I want those key handlers to kick in. How do I accomplish this?
Just use this:
private void TextBoxAllowed_OnKeyDown(object sender, KeyEventArgs e)
{
if (!yourTextBox.HasFocus())
e.Handled = true;
}

Is there a way in XAML to select all text in textbox when double clicked?

Is there a way to highlight all text in textbox purely through XAML, or does it have to be done in the Xaml.cs
Thanks!
This is what you are going to do:
First, add DoubleClickBehavior.cs class to your Project.
class DoubleClickBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
AssociatedObject.MouseDoubleClick += AssociatedObjectMouseDoubleClick;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseDoubleClick -= AssociatedObjectMouseDoubleClick;
base.OnDetaching();
}
private void AssociatedObjectMouseDoubleClick(object sender, RoutedEventArgs routedEventArgs)
{
AssociatedObject.SelectAll();
}
}
Then in .xaml, add this behavior to your TextBox:
<TextBox>
<i:Interaction.Behaviors>
<local:DoubleClickBehavior/>
</i:Interaction.Behaviors>
</TextBox>
You need to add two namepsaces to your .xaml to use your behavior. (The name of my project was WpfApplication1, so you will probably need to change that):
xmlns:local ="clr-namespace:WpfApplication1"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
That's it. Also you need System.Windows.Interactivity.dll to use the Behavior class.
You can download it from the Nuget Package Manager.
With a TextBox, you can add the PreviewMouseDoubleClick event.
<TextBox DockPanel.Dock="Top" Name="MyTextBox" AcceptsReturn="True" PreviewMouseDoubleClick="TextBoxSelectAll"/>
Then you set the TextBox.SelectedText Property of the TextBox to the text in the TextBox.
private void TextBoxSelectAll(object sender, MouseButtonEventArgs e) {
// Set the event as handled
e.Handled = true;
// Select the Text
(sender as TextBox).SelectAll();
}

Bubbling Events not Occurring

In the code below, I am seeing the tunneling events occurring but am not seeing the corresponding bubbling events occurring. Why might this be?
Thanks,
Dave
<Window x:Class="TestRoutedEvents.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
PreviewMouseUp="Window_PreviewMouseUp"
MouseUp="Window_MouseUp">
<Grid Background="Brown" Margin="30"
PreviewMouseUp="Grid_PreviewMouseUp"
MouseUp="Grid_MouseUp">
<TextBlock Text="Press me" HorizontalAlignment="Center" VerticalAlignment="Center"
Background="LightGray"
Padding="3"
PreviewMouseUp="TextBlock_PreviewMouseUp"
MouseUp="TextBlock_MouseUp"/>
</Grid>
</Window>
namespace TestRoutedEvents
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void TextBlock_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("TextBlock_PreviewMouseUp");
}
private void Grid_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Grid_PreviewMouseUp");
}
private void Window_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Window_PreviewMouseUp");
}
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("TextBlock_MouseUp");
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Grid_MouseUp");
}
private void Window_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Window_MouseUp");
}
}
}
Actually event is raising but you are not able to get that. The reason is that the messagbox takes the focus from the window when it pops up. Thus, the UI elements in the routed event chain won't receive the routed event any more.
you can have a control to your window and add event details into that to confirm about it.
e.g. Add ListBox in xaml and name it like listBox and then use this code in each of your handler
listBox.Items.Add(sender+"\n"+e.RoutedEvent.Name+"\n"+e.RoutingStrategy);
This is a quote from MSDN:
TextBox has built-in handling for the
bubbling MouseUp and events.
Consequently, custom event handlers
that listen for MouseUp or MouseDown
events from a TextBox will never be
called. If you need to respond to
these events, listen for the tunneling
PreviewMouseUp and PreviewMouseDown
events instead.
The article is about TextBox not TextBlock, but I tested this on other UIElement-derived controls, and they all seemed to behave the same way. I'm assuming this is at the base class level somewhere in the hierarchy.
How to: Handle MouseUp and MouseDown Events for a TextBox
I don't know what you are trying to do with this, but perhaps this post will help. It confirms what Anurag is saying, regarding the messagebox taking the focus from your window.
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/f4d609d4-ba2c-478e-aa53-9ee557ea5165

Categories