I have designed a reuseable usercontrol. It contains UserControl.InputBindings. It is quite simple as it only contains a label and a button (and new properties etc.)
When I use the control in my window it works well. But the key binding only works when focussed. When one control has a binding to alt+f8 this shortcut only works when it is focussed. When the other one with its own binding is focussed, that one works but alt+f8 no more. When none of the controls has the focus, nothing works.
How can I achieve that my usercontrol defines window-wide keybindings?
Especially following MVVM design pattern (Caliburn.Micro used) but any help is appreciated.
The XAML of the user control:
<UserControl x:Class="MyApp.UI.Controls.FunctionButton"
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:local="clr-namespace:MyApp.UI.Controls"
xmlns:cm="http://www.caliburnproject.org"
x:Name="Root"
Focusable="True"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="120">
<UserControl.Resources>
...
</UserControl.Resources>
<UserControl.InputBindings>
<KeyBinding Key="{Binding ElementName=Root, Path=FunctionKey}" Modifiers="{Binding ElementName=Root, Path=KeyModifiers}" Command="{Binding ElementName=Root, Path=ExecuteCommand}" />
</UserControl.InputBindings>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Top" Text="{Binding ElementName=Root, Path=HotkeyText}" />
<Button DockPanel.Dock="Bottom" Content="{Binding ElementName=Root, Path=Caption}" cm:Message.Attach="[Event Click] = [Action ExecuteButtonCommand($executionContext)]" cm:Action.TargetWithoutContext="{Binding ElementName=Root}" />
</DockPanel>
</UserControl>
Example usage:
<Grid>
<c:FunctionButton Width="75" Height="75" Margin="10,10,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" FunctionKey="F1" ShiftModifier="True" cm:Message.Attach="[Event Execute] = [Action Button1Execute]" />
<c:FunctionButton Width="75" Height="75" Margin="10,90,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" FunctionKey="F2" ShiftModifier="True" cm:Message.Attach="[Event Execute] = [Action Button2Execute]" />
</Grid>
As said each button works (Execute gets fired) on mouse click and when focused I can use space to activate the button and the input binding of the focused button works but never of the un-focused.
InputBindings won't be executed for a control that isn't focused because of the way they work - a handler for the input binding is searched in the visual tree from the focused element to the visual tree's root (the window). When a control is not focused, he won't be a part of that search path.
As @Wayne has mentioned, the best way to go would be simply move the input bindings to the parent window. Sometimes however this isn't possible (for example when the UserControl isn't defined in the window's xaml file).
My suggestion would be to use an attached behavior to move these input bindings from the UserControl to the window. Doing so with an attached behavior also has the benefit of being able to work on any FrameworkElement
and not just your UserControl. So basically you'll have something like this:
public class InputBindingBehavior
{
public static bool GetPropagateInputBindingsToWindow(FrameworkElement obj)
{
return (bool)obj.GetValue(PropagateInputBindingsToWindowProperty);
}
public static void SetPropagateInputBindingsToWindow(FrameworkElement obj, bool value)
{
obj.SetValue(PropagateInputBindingsToWindowProperty, value);
}
public static readonly DependencyProperty PropagateInputBindingsToWindowProperty =
DependencyProperty.RegisterAttached("PropagateInputBindingsToWindow", typeof(bool), typeof(InputBindingBehavior),
new PropertyMetadata(false, OnPropagateInputBindingsToWindowChanged));
private static void OnPropagateInputBindingsToWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)d).Loaded += frameworkElement_Loaded;
}
private static void frameworkElement_Loaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
frameworkElement.Loaded -= frameworkElement_Loaded;
var window = Window.GetWindow(frameworkElement);
if (window == null)
{
return;
}
// Move input bindings from the FrameworkElement to the window.
for (int i = frameworkElement.InputBindings.Count - 1; i >= 0; i--)
{
var inputBinding = (InputBinding)frameworkElement.InputBindings[i];
window.InputBindings.Add(inputBinding);
frameworkElement.InputBindings.Remove(inputBinding);
}
}
}
Usage:
<c:FunctionButton Content="Click Me" local:InputBindingBehavior.PropagateInputBindingsToWindow="True">
<c:FunctionButton.InputBindings>
<KeyBinding Key="F1" Modifiers="Shift" Command="{Binding FirstCommand}" />
<KeyBinding Key="F2" Modifiers="Shift" Command="{Binding SecondCommand}" />
</c:FunctionButton.InputBindings>
</c:FunctionButton>