我设计了一个可重复使用的用户控件。它包含UserControl.InputBindings。它非常简单,因为它只包含一个标签和一个按钮(以及新的属性等)。
当我在窗口中使用控件时效果很好。但密钥绑定仅在集中时才起作用。当一个控件具有对alt + f8的绑定时,此快捷方式仅在聚焦时才有效。当具有自己的绑定的另一个被聚焦时,那个可以工作但不再是alt + f8。当没有任何控件具有焦点时,没有任何效果。
如何实现我的usercontrol定义窗口范围的键绑定?
特别是遵循MVVM设计模式(Caliburn.Micro使用),但感谢任何帮助。
用户控件的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>
使用示例:
<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>
正如所说的每个按钮在鼠标点击时工作(执行被触发),当聚焦时,我可以使用空间来激活按钮,聚焦按钮的输入绑定可以工作,但从不聚焦。
答案 0 :(得分:32)
对于因为它们的工作方式而没有聚焦的控件,不会执行InputBindings - 在视觉树中从聚焦元素到可视树搜索输入绑定的处理程序&#39 ; s root(窗口)。当控件没有聚焦时,他不会成为该搜索路径的一部分。
正如@Wayne所提到的,最好的方法是将输入绑定移动到父窗口。但有时候这是不可能的(例如,当在窗口的xaml文件中没有定义UserControl时)。
我的建议是使用附加行为将这些输入绑定从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);
}
}
}
用法:
<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>
答案 1 :(得分:3)
是的,UserControl KeyBindings仅在控件具有焦点时才起作用。
如果您希望KeyBinding在窗口上工作,则必须在窗口本身上定义它。您可以使用以下命令在Windows XAML上执行此操作:
<Window.InputBindings>
<KeyBinding Command="{Binding Path=ExecuteCommand}" Key="F1" />
</Window.InputBindings>
但是你已经说过你想让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;
}
在这种情况下,ViewModel.FunctionKey必须是Key类型,否则您需要从字符串转换为Key类型。
必须在代码隐藏而不是XAML中执行此操作不会破坏MVVM模式。所有这一切都是将绑定逻辑从XAML转移到C#。 ViewModel仍然独立于View,因此可以在不实例化View的情况下进行单元测试。将这些 UI特定的逻辑放在视图的代码隐藏中是绝对正确的。
答案 2 :(得分:0)
@Entity
public class BasicTestEntity {
@Id @GeneratedValue private Integer id;
private String name;
}
public class BasicTestEntityDao
extends GenericDaoImpl<BasicTestEntity, Integer> {
}
keyPressPlaceHoler是目标元素的容器名称
记得在usercontrol中设置Focusable =“True”
答案 3 :(得分:0)
还有些晚,也许不是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);
}
}
由于这仅影响视图层,因此就MVVM而言,使用此解决方案很好。发现了这个here
位答案 4 :(得分:0)
我们在 UnLoaded 上通过取消订阅机制扩展了Adi Lesters附加的行为代码,以清理传输的绑定。如果控件退出可视树,则从窗口中删除InputBindings以避免它们处于活动状态。 (我们没有探索在附加属性上使用WPF-Triggers。)
随着WPF在我们的解决方案中重复使用控件,该行为不会脱离:已加载 / 已卸载不止一次。这不会导致泄漏,因为该行为不包含对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);
}
}
}
以某种方式在我们的解决方案中,某些控件没有抛出 UnLoaded 事件,尽管它们再也不会被使用,甚至过一会儿也会被垃圾收集。我们通过使用HashCode / WeakReferences进行跟踪并获取InputBindings的副本来解决此问题。
完整的类是:
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);
}
}
}
}