wpf InputBindings 仅在聚焦时工作
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23316274/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
InputBindings work only when focused
提问by ZoolWay
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.)
我设计了一个可重复使用的用户控件。它包含 UserControl.InputBindings。它非常简单,因为它只包含一个标签和一个按钮(以及新属性等)
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.
当我在窗口中使用控件时,它运行良好。但是键绑定仅在聚焦时有效。当一个控件绑定到 alt+f8 时,此快捷方式仅在聚焦时有效。当另一个有自己绑定的人被聚焦时,那个人可以工作,但 alt+f8 不再有效。当所有控件都没有焦点时,什么都不起作用。
How can I achieve that my usercontrol defines window-wide keybindings?
如何实现我的用户控件定义窗口范围的键绑定?
Especially following MVVM design pattern (Caliburn.Micro used) but any help is appreciated.
特别是遵循 MVVM 设计模式(使用 Caliburn.Micro)但任何帮助表示赞赏。
The XAML of the user control:
用户控件的 XAML:
<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.
如上所述,每个按钮在鼠标单击时起作用(执行被触发),当聚焦时,我可以使用空间来激活按钮,并且聚焦按钮的输入绑定起作用,但从未聚焦过。
回答by Adi Lester
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.
InputBindings 不会为由于其工作方式而未聚焦的控件执行 - 在可视化树中从聚焦元素到可视化树的根(窗口)搜索输入绑定的处理程序。当控件未聚焦时,他将不会成为该搜索路径的一部分。
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).
正如@Wayne 所提到的,最好的方法是简单地将输入绑定移动到父窗口。然而,有时这是不可能的(例如,当窗口的 xaml 文件中未定义 UserControl 时)。
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 FrameworkElementand not just your UserControl. So basically you'll have something like this:
我的建议是使用附加行为将这些输入绑定从 UserControl 移动到窗口。使用附加行为这样做还有一个好处,即能够处理任何FrameworkElement而不只是您的 UserControl。所以基本上你会有这样的事情:
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>
回答by Wayne Maurer
Yes, UserControl KeyBindings will only work when the control has focus.
是的,UserControl KeyBindings 仅在控件具有焦点时才起作用。
If you want the KeyBinding to work on the window, then you have to define it on the window itself. You do that on the Windows XAML using :
如果您希望 KeyBinding 在窗口上工作,那么您必须在窗口本身上定义它。您可以使用以下命令在 Windows XAML 上执行此操作:
<Window.InputBindings>
<KeyBinding Command="{Binding Path=ExecuteCommand}" Key="F1" />
</Window.InputBindings>
However you have said you want the UserControl to define the KeyBinding. I don't know of any way to do this in XAML, so you would have to set up this in the code-behind of the UserControl. That means finding the parent Window of the UserControl and creating the KeyBinding
但是,您已经说过您希望 UserControl 定义 KeyBinding。我不知道在 XAML 中有什么方法可以做到这一点,因此您必须在 UserControl 的代码隐藏中进行设置。这意味着找到 UserControl 的父窗口并创建 KeyBinding
{
var window = FindVisualAncestorOfType<Window>(this);
window.InputBindings.Add(new KeyBinding(ViewModel.ExecuteCommand, ViewModel.FunctionKey, ModifierKeys.None));
}
private T FindVisualAncestorOfType<T>(DependencyObject d) where T : DependencyObject
{
for (var parent = VisualTreeHelper.GetParent(d); parent != null; parent = VisualTreeHelper.GetParent(parent)) {
var result = parent as T;
if (result != null)
return result;
}
return null;
}
The ViewModel.FunctionKey would need to be of type Key in this case, or else you'll need to convert from a string to type Key.
在这种情况下,ViewModel.FunctionKey 需要是 Key 类型,否则您需要从字符串转换为 Key 类型。
Having to do this in code-behind rather than XAML does not break the MVVM pattern. All that is being done is moving the binding logic from XAML to C#. The ViewModel is still independent of the View, and as such can be Unit Tested without instantiating the View. It is absolutely fine to put such UI specificlogic in the code-behind of a view.
必须在代码隐藏而不是 XAML 中执行此操作不会破坏 MVVM 模式。所做的一切就是将绑定逻辑从 XAML 转移到 C#。ViewModel 仍然独立于视图,因此可以在不实例化视图的情况下进行单元测试。将此类UI 特定逻辑放在视图的代码隐藏中绝对没问题。
回答by Jan
Yet a bit late and possibly not 100% MVVM conform, one can use the following onloaded-event to propagate all Inputbindings to the window.
然而有点晚了,可能不是 100% MVVM 符合,可以使用以下 onloaded-event 将所有 Inputbindings 传播到窗口。
void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(this);
foreach (InputBinding ib in this.InputBindings)
{
window.InputBindings.Add(ib);
}
}
Since this only affects the View-Layer I would be fine with this solution in terms of MVVM. found this bit here
由于这仅影响视图层,因此就 MVVM 而言,我对这个解决方案没问题。在这里找到了这一点
回答by Leonidas
We extended Adi Lestersattached behavior code with an unsubscribing mechanism on UnLoadedto clean up the transferred bindings. If the control exits the Visual Tree, the InputBindings are removed from the Window to avoid them being active. (We did not explore using WPF-Triggers on the attached property.)
我们使用UnLoaded上的取消订阅机制扩展了Adi Lesters附加的行为代码,以清理传输的绑定。如果控件退出可视化树,则 InputBindings 将从窗口中删除以避免它们处于活动状态。(我们没有探索在附加属性上使用 WPF-Triggers。)
As controls get reused by WPF in our solution, the behavior does not detach: Loaded/UnLoadedget called more than once. This does not lead to leaking, as the behavior doesn't hold a reference to the FrameWorkElement.
当控件在我们的解决方案中被 WPF 重用时,行为不会分离:Loaded/ UnLoaded被多次调用。这不会导致泄漏,因为该行为不包含对 FrameWorkElement 的引用。
private static void OnPropagateInputBindingsToWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)d).Loaded += OnFrameworkElementLoaded;
((FrameworkElement)d).Unloaded += OnFrameworkElementUnLoaded;
}
private static void OnFrameworkElementLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
var window = Window.GetWindow(frameworkElement);
if (window != null)
{
// transfer InputBindings into our control
if (!trackedFrameWorkElementsToBindings.TryGetValue(frameworkElement, out var bindingList))
{
bindingList = frameworkElement.InputBindings.Cast<InputBinding>().ToList();
trackedFrameWorkElementsToBindings.Add(
frameworkElement, bindingList);
}
// apply Bindings to Window
foreach (var inputBinding in bindingList)
{
window.InputBindings.Add(inputBinding);
}
frameworkElement.InputBindings.Clear();
}
}
private static void OnFrameworkElementUnLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
var window = Window.GetWindow(frameworkElement);
// remove Bindings from Window
if (window != null)
{
if (trackedFrameWorkElementsToBindings.TryGetValue(frameworkElement, out var bindingList))
{
foreach (var binding in bindingList)
{
window.InputBindings.Remove(binding);
frameworkElement.InputBindings.Add(binding);
}
trackedFrameWorkElementsToBindings.Remove(frameworkElement);
}
}
}
Somehow in our solution some controls are not throwing the UnLoadedevent, although they never get used again and even get garbage collected after a while. We are taking care of this with tracking with HashCode/WeakReferences and taking a copy of the InputBindings.
不知何故,在我们的解决方案中,一些控件没有抛出UnLoaded事件,尽管它们再也不会被使用,甚至在一段时间后会被垃圾回收。我们通过使用 HashCode/WeakReferences 进行跟踪并获取 InputBindings 的副本来处理此问题。
Full class is:
全班是:
public class InputBindingBehavior
{
public static readonly DependencyProperty PropagateInputBindingsToWindowProperty =
DependencyProperty.RegisterAttached("PropagateInputBindingsToWindow", typeof(bool), typeof(InputBindingBehavior),
new PropertyMetadata(false, OnPropagateInputBindingsToWindowChanged));
private static readonly Dictionary<int, Tuple<WeakReference<FrameworkElement>, List<InputBinding>>> trackedFrameWorkElementsToBindings =
new Dictionary<int, Tuple<WeakReference<FrameworkElement>, List<InputBinding>>>();
public static bool GetPropagateInputBindingsToWindow(FrameworkElement obj)
{
return (bool)obj.GetValue(PropagateInputBindingsToWindowProperty);
}
public static void SetPropagateInputBindingsToWindow(FrameworkElement obj, bool value)
{
obj.SetValue(PropagateInputBindingsToWindowProperty, value);
}
private static void OnPropagateInputBindingsToWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)d).Loaded += OnFrameworkElementLoaded;
((FrameworkElement)d).Unloaded += OnFrameworkElementUnLoaded;
}
private static void OnFrameworkElementLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
var window = Window.GetWindow(frameworkElement);
if (window != null)
{
// transfer InputBindings into our control
if (!trackedFrameWorkElementsToBindings.TryGetValue(frameworkElement.GetHashCode(), out var trackingData))
{
trackingData = Tuple.Create(
new WeakReference<FrameworkElement>(frameworkElement),
frameworkElement.InputBindings.Cast<InputBinding>().ToList());
trackedFrameWorkElementsToBindings.Add(
frameworkElement.GetHashCode(), trackingData);
}
// apply Bindings to Window
foreach (var inputBinding in trackingData.Item2)
{
window.InputBindings.Add(inputBinding);
}
frameworkElement.InputBindings.Clear();
}
}
private static void OnFrameworkElementUnLoaded(object sender, RoutedEventArgs e)
{
var frameworkElement = (FrameworkElement)sender;
var window = Window.GetWindow(frameworkElement);
var hashCode = frameworkElement.GetHashCode();
// remove Bindings from Window
if (window != null)
{
if (trackedFrameWorkElementsToBindings.TryGetValue(hashCode, out var trackedData))
{
foreach (var binding in trackedData.Item2)
{
frameworkElement.InputBindings.Add(binding);
window.InputBindings.Remove(binding);
}
trackedData.Item2.Clear();
trackedFrameWorkElementsToBindings.Remove(hashCode);
// catch removed and orphaned entries
CleanupBindingsDictionary(window, trackedFrameWorkElementsToBindings);
}
}
}
private static void CleanupBindingsDictionary(Window window, Dictionary<int, Tuple<WeakReference<FrameworkElement>, List<InputBinding>>> bindingsDictionary)
{
foreach (var hashCode in bindingsDictionary.Keys.ToList())
{
if (bindingsDictionary.TryGetValue(hashCode, out var trackedData) &&
!trackedData.Item1.TryGetTarget(out _))
{
Debug.WriteLine($"InputBindingBehavior: FrameWorkElement {hashCode} did never unload but was GCed, cleaning up leftover KeyBindings");
foreach (var binding in trackedData.Item2)
{
window.InputBindings.Remove(binding);
}
trackedData.Item2.Clear();
bindingsDictionary.Remove(hashCode);
}
}
}
}
回答by mao
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=keyPressPlaceHoler}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Style>
keyPressPlaceHoler is the name of container of your target uielement
keyPressPlaceHoler 是目标 uielement 的容器名称
remember to set the Focusable="True" in usercontrol
记得在用户控件中设置 Focusable="True"

