KeyDown event binding to user control - c#

I'm currently working on a WPF project using Caliburn.Micro and want to bind a KeyDown event to a UserControl. It should be fired if the Window is opened and the user pushes any button.
<UserControl x:Class="Test.Views.AppView"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cal="http://www.caliburnproject.org"
cal:Message.Attach="[Event KeyDown] = [TestMethod($executionContext)]" />
Unfortunately this code doesn't work. Is it even possible to bind an Event to the UserControl and not to specific controls like TextBox or Button?

If a control on this UserControl has focus it will receive the KeyDown event first and handle it. This will prevent the UserControl from receiving it.
Use PreviewKeyDown to capture the event. The Preview... events are exactly for this kind of scenario. They bubble up from the root to the child controls whereas the regular events tunnel down.
Don't forget to set e.Handled = true; at the end of a handler if the bubbling and tunneling should stop.

Related

CaliburnMicro IsVisibleChanged does not fire

The event IsVisibleChanged couldn't be routed to the ViewModel. What could be the cause?
If I'm testing the event as normal WPF event (no Caliburn Message.Atach) with CodeBehind, the Event is fired as expected. If I'm testing the Caliburn Message.Atach with other events of the UserControl like LayoutUpdated, they work like expected with the ViewModel. But I'm not able to get IsVisibleChanged fired to my ViewModel.
View
<UserControl x:Class="MySetupDeviceConfig.Views.SetupDeviceConfigView"
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"
xmlns:cal="http://www.caliburnproject.org"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Visibility="{Binding Visibility}"
d:DesignHeight="450" d:DesignWidth="800"
cal:Message.Attach="[Event IsVisibleChanged] = [Action UcIsVisibleChanged];">
<Grid>
...
ViewModel
public class SetupDeviceConfigViewModel : Screen
{
private Visibility _Visibility;
private ILogger Log { get; set; }
public Visibility Visibility { get => _Visibility; set { _Visibility = value; NotifyOfPropertyChange(); } }
// ...
public void UcIsVisibleChanged()
{
Log.LogInformation("IsVisibleChanged");
}
Tested with Caliburn.Micro v4.0.62-alpha and CaliburnMicro v3.2.0
Changing e.g. to the Loaded event in the view with same action/function mapping -> it works. So there is no type mismatch...
cal:Message.Attach="[Event Loaded] = [Action UcIsVisibleChanged];">
The reason for this not working is that IsVisibleChanged is a CLR event and not a routed event. As stated in the documentation.
Caliburn.Micro's Message system works on routed event not CLR events. Since Caliburn.Micro uses EventTrigger internally.
Why not just make a binding to IsVisible?
Doesn't your property need to be called IsVisible rather than Visibility? Or, change the call to NotifyOfPropertyChange from the default to NotifyOfPropertyChange("IsVisible").
Try placing the action on the first element (the Grid) inside your UserControl rather than on the UserControl itself. I tried it myself and it seemed to work on the Grid, but not on the UserControl itself, could be a Caliburn bug?
I noticed also that when I toggled the Visibility of the Grid in code-behind, the event didn't get fired, but when I bound the Visibility dependency property to a property in my ViewModel, it worked! Seems like another bug in Caliburn.
I think it's usually good practice to place events and bindings on controls inside the UserControl rather than the UserControl itself. If the UserControl gets hidden from the outside, the Grid inside it shoud fire the visibility event anyway, so it practically makes no difference.

RoutedEventArgs.Source give me wrong value? [duplicate]

I am new to WPF and am going through the examples of Professional WPF in .net 4.5. In the commands chapter, there is an example where multiple controls can send the same command. I am using a Button, CheckBox and MenuItem to trigger the New command.
The issue I am facing is that if MenuItem is pressed for the first time, the source shows correctly. However, after clicking the Button or CheckBox, then clicking MenuItem shows me the source of the last control Button or CheckBox, whichever was pressed. I couldn't find what was wrong with my code or why is this behavior shown by MenuItem in WPF.
Below is the code.
<Window x:Class="WpfApplication1.CommandSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CommandSample" Height="300" Width="300">
<Window.CommandBindings>
<CommandBinding Command="New" Executed="CommandBinding_Executed" />
</Window.CommandBindings>
<StackPanel>
<Button Command="New" MaxWidth="80" MaxHeight="30" Content="{x:Static ApplicationCommands.New}" />
<Menu MaxHeight="30" VerticalAlignment="Top">
<MenuItem Header="File">
<MenuItem Command="New"></MenuItem>
</MenuItem>
</Menu>
<CheckBox Command="New"></CheckBox>
</StackPanel>
</Window>
namespace WpfApplication1 {
public partial class CommandSample: Window {
public CommandSample() {
InitializeComponent();
}
private void CommandBinding_Executed(object sender,ExecutedRoutedEventArgs e)
{
MessageBox.Show("New Command launched by " + e.Source);
}
}
}
Yes this is correct (or at least that's how it's designed). Routed commands start routing based on the CommandTarget you specify. If one isn't specified typically the object raising the event uses itself as the starting point (so the MenuItem in this case). So the routing starts with the MenuItem in this case as you might expect. Nothing handles it there so the CommandManager goes up the parent chain. When it hits an element that is a FocusScope (like the Menu), it checks the FocusedElement of the "parent" FocusScope (e.g. the FocusScope of the parent of the Menu which in this case is the Window). If there is a FocusedElement (which there will be one once you have focused an element in the window's focus scope such as your button, checkbox, a textbox that you might put in that stackpanel, etc.) then the CommandManager starts routing the event from that element. When it does that it creates a new ExecutedRoutedEventArgs where the OriginalSource is that starting element (so the button, checkbox, textbox) and then continues routing up the tree.
So when you first ran the app, the FocusedElement of the Window (that's the root focus scope in your example) is null so there is no re-routing needed so the CommandManager just kept going up the parent chain past the Menu and that is why MenuItem appeared as the Source & OriginalSource. When you clicked on the Button you gave that keyboard focus and as part of it also became the logically focused element of its focus scope (i.e. the FocusedElement of its containing FocusScope). So when the MenuItem was subsequently clicked and the CommandManager ultimately reached the Menu, it then re-routed over to the Button (or whatever you focused in the window's focusscope) and started routing up from there. I say this is expected because with routed command you want the routing to go through the logically focused element so that for example, the Cut command of a menu item would trigger a cut of the TextBox in the Window that had focus.

KeyDown event not raising from a grid

Here I have sample window with a grid. I need to capture event when key is pressed. But it is not raising when I click grid area and then press key. It will work only if Textbox is focused. I know it will work if I capture it from Window. But I have other application with few usercontrols and I need to capture it from distinct ones. I tried to set Focusable.false for Window and true for Grid but it not helps.
Any solutions?
<Window x:Class="Beta.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" Closed="Window_Closed_1" Focusable="False">
<Grid KeyDown="Grid_KeyDown_1" Focusable="True">
<TextBox x:Name="tbCount" HorizontalAlignment="Left" Height="35" Margin="310,49,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="83"/>
</Grid>
Right this is weird. This is clearly a focus problem, still I can not understand why the grid do not take Focus, even when we click on it.
Though there is a workaround: create an handler for the loaded event of the grid:
<Grid x:Name="theGrid" KeyDown="Grid_KeyDown_1" Focusable="True" Loaded="TheGrid_OnLoaded">
And then force focus in your code behind:
private void TheGrid_OnLoaded(object sender, RoutedEventArgs e)
{
theGrid.Focus();
}
Your keydown event will work after that.
Hope it helps.
I had the same issue with a Universal Windows Platform (UWP) app. I attached the event to a grid in XAML but it would only work when the focus was on the TextBox. I found the answer (not just a workaround) on MSDN: https://social.msdn.microsoft.com/Forums/en-US/56272bc6-6085-426a-8939-f48d71ab12ca/page-keydown-event-not-firing?forum=winappswithcsharp
In summary, according to that post, the event won't fire because when focus to the TextBox is lost, it's passed higher up so the Grid won't get it. Window.Current.CoreWindow.KeyDown should be used instead. I've added my event handlers to the page loaded event like this:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
Window.Current.CoreWindow.KeyDown += coreWindow_KeyDown;
Window.Current.CoreWindow.KeyUp += CoreWindow_KeyUp;
}
This works as expected for me.
I tried using the Focus Method too, to no avail, until I set the Focusable property to true ( It was default to False. )
I had the same problem, I've used PreviewKeyDownevent and it worked for me.

How to handle TextBlock.KeyDown event, when TextBlock is a part of UserControl?

I have this simple UserControl:
<UserControl x:Class="WPFTreeViewEditing.UserControl1"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="Hello, world!" KeyDown="TextBlock_KeyDown" />
</Grid>
</UserControl>
I want to handle TextBlock.KeyDown event. So, I've added an event handler to the code-behind:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void TextBlock_KeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show("Key up!");
}
}
but it doesn't fire. What's wrong?
UPDATE.
PreviewKeyDown doesn't fire too.
This UserControl is used in HierarchicalDataTemplate then:
<Window x:Class="WPFTreeViewEditing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTreeViewEditing"
Title="MainWindow" Height="265" Width="419">
<Grid>
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ViewModel}" ItemsSource="{Binding Items}">
<local:UserControl1 />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
From the documentation for UIElement.KeyDown:
Occurs when a key is pressed while focus is on this element.
You're using TextBlock which doesn't have the focus, so your KeyDown event will be handled by another control.
You can switch to TextBox and appy some styles so it'll look and behave like TextBlock, but you'll be able to get the focus and handle the event.
You should use PreviewKeyDown event instead of KeyDown event.
Ok, even though this question was posted a long time ago I had the same problem and found a way to get KeyDown events working, though it might not be what you're looking for I'll post the code to help future people with the same problem.
First thing first a KeyDown event handler in an Xaml Object will only fire off if that object has focus. Therefore you need a CoreWindow event handler, it's kind off the same thing but it will always run no matter what object or thing has focus. The following will be the code.
//CLASS.xaml.h
ref class CLASS{
private:
Platform::Agile<Windows::UI::Core::CoreWindow> window;
public:
void KeyPressed(Windows::UI::Core::CoreWindow^ Window, Windows::UI::Core::KeyEventArgs^ Args);
//CLASS.xaml.cpp
CLASS::CLASS(){
InitializeComponent();
window = Window::Current->CoreWindow;
window->KeyDown += ref new TypedEventHandler
<Windows::UI::Core::CoreWindow^, Windows::UI::Core::KeyEventArgs^>(this, &CLASS::KeyPressed);
};
void CLASS::KeyPressed(Windows::UI::Core::CoreWindow^ Window, Windows::UI::Core::KeyEventArgs^ Args){
SimpleTextBox->Text = Args->VirtualKey.ToString();
};
Basically you want a value to hold your window and use that to create a new TypedEventHandler. For safety you'll generally want to do this in your class' constructor a function that's only called once the moment the class starts (I still prefer the constructor though).
You can use this method to create an event handler for any event. Just change the "KeyDown" for another attribute like KeyUp, PointerMoved, PointerPressed and change the "&CLASS::KeyPressed" to the name of the function you want to be fired the moment you get an event of a corresponding type.

Why doesn't keyboard input work for a ScrollViewer when the child control has input focus?

Why doesn't keyboard input work for a ScrollViewer when the child control has input focus?
This is the scenario. A WPF window opens. It sets the focus to a control that is embedded in a ScrollViewer.
I hit the up and down and left and right keys. The ScrollViewer doesn't seem to handle the key events, anyone know why?
This is the simplest possible example:
<Window x:Class="WpfApplication1.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"
FocusManager.FocusedElement="{Binding ElementName=control}"
>
<Grid>
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
>
<ItemsControl
x:Name="control"
Width="1000"
Height="1000"
/>
</ScrollViewer>
</Grid>
</Window>
When you start the app that contains this window, "control" appears to have the focus as I intended. Pressing the key seems to result in bubbling key events reaching the ScrollViewer (I checked for this using WPF Snoop). I can't work out why it doesn't respond to the input.
The problem
A ScrollViewer ignores all KeyDown events whose OriginalSource is not the ScrollViewer. The OriginalSource on a KeyDown is set to the focused control, therefore the ScrollViewer ignores it when a child has the focus.
The solution
Catch the KeyDown event and raise a copy of it directly on the ScrollViewer so it will have the correct OriginalSource, like this:
void ScrollViewer_KeyDown(object sender, KeyEventArgs e)
{
if(e.Handled) return;
var temporaryEventArgs =
new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key)
{
RoutedEvent = e.RoutedEvent
};
// This line avoids it from resulting in a stackoverflowexception
if (sender is ScrollViewer) return;
((ScrollViewer)sender).RaiseEvent(temporaryEventArgs);
e.Handled = temporaryEventArgs.Handled;
}
the event handler can be added in XAML:
<ScrollViewer KeyDown="ScrollViewer_KeyDown" />
or in code:
scrollViewer.AddHandler(Keyboard.KeyDownEvent, ScrollViewer_KeyDown);
The latter is more applicable if the ScrollViewer is inside a template somewhere and you have code to find it.
In order for the ScrollViewer to react to your keyboard keys - it needs to have IsFocused="True" - right now it's child has the focus.
To prove it - try in your Loaded event to manually set focus to the ScrollViewer (you might have to set IsFocusable="True" for it to work on the ScrollViewer) - now the keys should work. If you want it to work otherwise, you need to set the appropriate EventHandlers on the ScrollViewer (KeyDown/KeyPress etc.)
Similar issue with no keyboard navigation when using ScrollViewer inside an ItemsControl.Template. Adding Focusable and IsTabStop resolved the issue in my case.
<ScrollViewer
Focusable="True"
IsTabStop="True">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>

Categories