InputBindings работают только при фокусировке

Я разработал повторно используемый usercontrol. Он содержит UserControl.InputBindings. Это довольно просто, поскольку он содержит только метку и кнопку (и новые свойства и т. д.)

когда я использую элемент управления в моем окне, он работает хорошо. Но связывание ключей работает только при фокусировке. Когда один элемент управления имеет привязку к alt+f8, этот ярлык работает только при фокусировке. Когда другой с его собственной привязкой фокусируется, этот работает, но alt+f8 больше не работает. Когда ни один из элементов управления не имеет фокус, ничего не работает.

Как я могу добиться того, что мой usercontrol определяет привязки клавиш в пределах окна?

специально следовать картиной конструкции MVVM (Caliburn.Микро используется), но любая помощь приветствуется.


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>

Как сказано, каждая кнопка работает (Execute gets fired) при щелчке мыши и при фокусировке я могу использовать пространство для активации кнопки и входной привязки сфокусированная кнопка работает, но никогда не фокусируется.

4 ответов


InputBindings не будут выполняться для элемента управления, который не сфокусирован из - за того, как они работают-обработчик для входной привязки ищется в визуальном дереве от сфокусированного элемента до корня визуального дерева (окно). Когда элемент управления не сфокусирован, он не будет частью этого пути поиска.

как упоминал @Wayne, лучшим способом было бы просто переместить входные привязки в родительское окно. Иногда, однако, это невозможно (например, когда UserControl не определен в xaml-файле окна).

мое предложение было бы использовать прикрепленное поведение для перемещения этих входных Привязок из 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>

да, привязки клавиш UserControl будут работать только тогда, когда элемент управления имеет фокус.

Если вы хотите, чтобы привязка клавиш работала на окне, вы должны определить ее в самом окне. Вы делаете это на Windows XAML, используя:

<Window.InputBindings>
  <KeyBinding Command="{Binding Path=ExecuteCommand}" Key="F1" />
</Window.InputBindings>

однако вы сказали, что хотите, чтобы UserControl определял привязку клавиш. Я не знаю, как это сделать в XAML, поэтому вам придется настроить это в коде позади UserControl. Это означает, что найти родительское окно UserControl и создание привязки клавиш

{
    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 должен иметь ключ типа, иначе вам нужно будет преобразовать строку в ключ типа.

необходимость делать это в коде, а не в XAML не нарушает шаблон MVVM. Все, что делается, - это перемещение логики привязки из XAML в C#. ViewModel по-прежнему не зависит от представления и поэтому может быть протестирован без создания экземпляра представления. Это абсолютно нормально ставь такие UI specific логика в коде представления.


<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-это имя контейнера вашего целевого uielement

Не забудьте установить Focusable= "True" в usercontrol


еще немного поздно и, возможно, не 100% MVVM соответствуют, можно использовать следующее onloaded-событие для распространения всех Inputbindings в окно.

void UserControl1_Loaded(object sender, RoutedEventArgs e)
    {
        Window window = Window.GetWindow(this);
        foreach (InputBinding ib in this.InputBindings)
        {
            window.InputBindings.Add(ib);
        }
    }

поскольку это влияет только на слой представления, я был бы в порядке с этим решением в терминах MVVM. нашел этот бит здесь