e.Handled = true is not stopping keystroke event from bubbling up - wpf

I have a TextBox contained inside a DockPanel. If Enter is pressed while typing in the TextBox, I want one thing to happen. If Enter is pressed elsewhere in the DockPanel, I want another thing to happen. What I don't want is for Both of the things to happen when Enter is pressed in the TextBox, but that's what's happening, even though I have e.Handled = true.
The pertinant XAML:
<DockPanel MinWidth="600" MinHeight="200" >
<DockPanel.InputBindings>
<KeyBinding Key="Return" Command="{Binding PlayCommand}" />
</DockPanel.InputBindings>
<Grid>
<Grid.ColumnDefinitions ... >
<Other Controls ...>
<DockPanel Grid.Column="2">
<Other Controls ...>
<DockPanel DockPanel.Dock="Top">
<Other Controls ...>
<TextBox Name="txtFilterText" Text="{Binding SmartFilterText}" PreviewKeyUp="txtFilterText_PreviewKeyUp">
</TextBox>
</DockPanel>
</DockPanel>
</Grid>
</DockPanel>
The code-behind:
Private Sub txtFilterText_PreviewKeyUp(sender As Object, e As KeyEventArgs)
' when we hit Enter on the smart filter text box
If e.Key = Key.Enter Then
' do the TextBox Enter stuff
e.Handled = True
End If
End Sub
When I press Enter in the TextBox, what happens is both the bound PlayCommand, and the PreviewKeyUp event handler run. I want only the PreviewKeyUp event handler code to run, and then stop there, and prevent the keyboard event from bubbling up to the PlayCommand on the containing DockPanel.
I've tried handling KeyUp instead of PreviewKeyUp, but it was the same.
I've tried putting e.Handled = True outside the If, but it was the same.
What is the correct way?

Key bindings are invoked in response to key down messages, by the time the user physically releases the key the command handler has already long been invoked.
Ergo, intercept PreviewKeyDown instead.

Related

TabItem Header looks different as a Label

I have a TabItem and was previously setting the Header within the object like so,
<TabItem x:Name="Checked_Out_Items" Header="Clients In Use" Height="40" LostFocus="Checked_Out_Items_LostFocus" GotFocus="Checked_Out_Items_GotFocus" BorderThickness="0" >
However, I've run into the problem where there is not a Click event for this Tab. I found a solution where we can insert a label as the header specified here,
how to handle TabItem single click event in WPF?
But, the Label looks nothing like what I need. Here is the Tab Header defined inside the object.
Here's what it looks like with the label
<TabItem x:Name="Checked_Out_Items" Height="40" LostFocus="Checked_Out_Items_LostFocus" GotFocus="Checked_Out_Items_GotFocus" BorderThickness="0" >
<TabItem.Header>
<Label Content="Clients In Use" Height="40" MouseLeftButtonDown="Checked_Out_Items_Clicked" Width="171" />
</TabItem.Header>
Lets separate the concerns a bit.
In order to handle a mouse click event, a control needs to be prepared for receiving this event, it needs to implement a handler and the handler must actually receive the event. In case of TabItem the mouse click event is consumed by the control, without releasing the event to user defined listeners.
On the level of TabItem, the best option would be to handle the PreviewMouseLeftButtonDown event, but this is not an option if event handling shouldn't occur when child controls have their own handling functionality.
So the other option is to handle the MouseLeftButtonDown event before it reaches the TabItem, which means to handle it in a child control of the tab item.
As said, in order to receive the event, the control needs to be ready to receive the event. This means, it needs a Background not null (can be Transparent) and IsHitTestEnabled="True" (which is default in most cases) and it must actually handle the event.
For the showcase, I use a Red instead of Transparent background color. The red area is the place where mouse clicks are captured and handled.
<TabItem Padding="0">
<TabItem.Header>
<Border Height="30" Width="50"
Background="Red" MouseLeftButtonDown="Item_MouseLeftButtonDown">
<ContentPresenter
VerticalAlignment="Center" HorizontalAlignment="Center"
Content="T2"/>
</Border>
</TabItem.Header>
Test Content 2
</TabItem>
Or, in order to have a better separation between header content and click handling, a HeaderTemplate can be used:
<TabItem Header="T3" Padding="0">
<TabItem.HeaderTemplate>
<DataTemplate>
<Border MinHeight="30" MinWidth="50"
Background="Red" MouseLeftButtonDown="Item_MouseLeftButtonDown">
<ContentPresenter
VerticalAlignment="Center" HorizontalAlignment="Center"
Content="{Binding}"/>
</Border>
</DataTemplate>
</TabItem.HeaderTemplate>
Test Content 3
</TabItem>
The issue with any approach that relies on child controls is, that there is still a border of the TabItem and if the user clicks that border, the click will be outside of the child control and the tab will be selected without executing the click handler.
So a different way to handle tab changes (not clicks!) would be to handle the TabControl.SelectionChanged event and filter for events that actually originate from the TabControl and not from some inner content elements.
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.Source == sender)
{
// this selection change is actually issued because of a tab change
}
}
Another thing I just realized: The same condition could be used in TabItem.PreviewMouseLeftButtonDown in order to filter clicks to the tabitem header vs click events originating from the content area.
Edit:
The reason why Label is looking different is, that a font style is active for TabItem and Label is using some of its internal styles, ignoring the TabItem style.

WPF: Mystery with RichTextBox and KeyDown Event

Before I explain, I will present code, because it is the best way to express the question in this case. a simple WPF window, his base node contains this:
<StackPanel>
<Menu>
<TextBox x:Name"A" Width="200" />
</Menu>
<TextBlock Margin="0,10,0,0">--richTB--</TextBlock>
<RichTextBox x:Name"B" />
<TextBlock Margin="0,10,0,0">--simpleTB--</TextBlock>
<TextBox x:Name"C" />
</StackPanel>
Now, if the focus is in the buttom TextBox (C), and by a Mouse-click I move focus to the top TextBox within menu (A), and then I hit Enter key, the focus returns to the buttom TextBox (C). This behavior of the MenuItem that Enter key "selects" and returns the focus. so far everything is normal.
But, if i focus RichTextBox (B), and then I go through the top TextBox (A), when I tap Return cursor will remain in the box.
I made another little test, and I listening the PreviewKeyDown & KeyDown events for the TextBox within menu (A). So:
<Menu>
<TextBox Width="200" KeyDown="TB_KeyDown" PreviewKeyDown="TB_PreviewKeyDown" />
</Menu>
What I discovered? The last scenario in which the focus was first RichTextBox (B) and then in TextBox within menu (A), there are no listening to the KeyDown for Enter key!

My OnCanExecute event happens repeatedly

I've got a TextBox in xaml that I want to be validated using the CanExecute option of the commands. Here I have the xaml:
<UserControl.Resources>
<RoutedUICommand x:Key="CloseCommand" Text=""/>
<RoutedUICommand x:Key="NextCommand" Text=""/>
<RoutedUICommand x:Key="BackCommand" Text=""/>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="{StaticResource CloseCommand}" Executed="Close" />
<CommandBinding Command="{StaticResource NextCommand}" Executed="Next" CanExecute="OnCanExecute" />
<CommandBinding Command="{StaticResource BackCommand}" Executed="Back" />
</UserControl.CommandBindings>
These are the CommandBindings that are called, where the Next command is tied to a CanExecute restriction. The button looks like this:
<Button
DockPanel.Dock="Right"
Name="ButtonNext"
Margin="0,0,15,0"
Style="{DynamicResource NextButton}"
Command="{StaticResource NextCommand}">
</Button>
And the .cs for the validation:
private void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ValidateFields();
}
Where the ValidateFields() method looks like this:
public bool ValidateFields()
{
if (string.IsNullOrEmpty(AutoCompleteBox.Text)) return false;
return IsValid(MyTextBox);
}
So my OnCanExecute is tied to a TextBox validation. But I've got a problem. Even if a leave the Window where this code is written, I noticed that this OnCanExecute is still being called.
Why is this? Why my OnCanExecute is being called even if it's not visible?
NextCommand is a RoutedCommand.
If you have a look at the implementation, you'll notice that CanExecuteChanged is fired whenever CommandManager.RequerySuggested is fired.
This will be in many circumstances. I don't know exactly but it may be as often as the mouse moves or a key is pressed (or other user interactions)!
You could add a counter variable in your Eventhandler could to have a look how often this happens.
If your RoutedCommand.CanExecute depends on something from "code-behind" and you don't move the mouse, press a key or some other user interaction, the CanExecuteChanged may not fire although it should.
You can test this if you make your CanExecute depend on a periodically switching bool.

WPF, Loaded event, IsLoaded property

If I attach a handler to the Loaded event of an element (no matter if it is in the VS designer or in the code), it sets the IsLoaded property to true.
For instance, I have a TabControl with two TabItems. There is a button on each TabItem. The first TabItem is the default item (selected when my window is shown). The Button on the second TabItem has attached the Loaded event. In this case, the button on the first TabItem has IsLoaded set to true and the button on the second TabItem should be set to false but it is set to true.
When I unset the Loaded event it works as expected - the first button has IsLoaded true, the second has false.
Is this a wpf bug or am I doing something wrong?
EDIT:
See an example.
This code causes the IsLoaded property of btn2 set to True (Note: the btn2_Loaded method is empty).
XAML:
<Window x:Class="WpfApplication1.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>
<TabControl>
<TabItem Header="Tab1">
<Button x:Name="btn1" Click="btn1_Click" />
</TabItem>
<TabItem Header="Tab2">
<Button x:Name="btn2" Loaded="btn2_Loaded" />
</TabItem>
</TabControl>
</Grid>
</Window>
C#:
private void btn1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this.btn2.IsLoaded.ToString());
}
Start the application and just click the btn1.
Now remove the Loaded handler:
<TabItem Header="Tab2">
<Button x:Name="btn2" />
</TabItem>
Start the application again and click the btn1.
Now the IsLoaded is False! Why is this happening? Attaching an event handler causes loading of the particular element?
This is neither a bug in WPF nor are you doing something wrong. The MSDN page on Object Lifetime Events says that
The Loaded event is raised before the final rendering, but after the
layout system has calculated all necessary values for rendering.
You can not make any more precise assumption on when exactly Loaded is called. In your case i guess WPF delays loading of the non-visible (i.e. not yet rendered) elements, unless a Loaded handler is attached. It is just free to do so.

"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.

Resources