11

再利用可能なユーザーコントロールを設計しました。UserControl.InputBindings が含まれています。ラベルとボタン (および新しいプロパティなど) しか含まれていないため、非常にシンプルです。

ウィンドウでコントロールを使用すると、うまく機能します。ただし、キーバインドはフォーカスされている場合にのみ機能します。1 つのコントロールが alt+f8 にバインドされている場合、このショートカットはフォーカスされている場合にのみ機能します。独自のバインディングを持つもう一方がフォーカスされると、それは機能しますが、alt+f8 は機能しなくなります。どのコントロールにもフォーカスがない場合、何も機能しません。

ユーザーコントロールがウィンドウ全体のキーバインドを定義するようにするにはどうすればよいですか?

特に 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>

前述のように、各ボタンはマウス クリックで動作し (Execute が起動されます)、フォーカスされるとスペースを使用してボタンをアクティブにできます。フォーカスされたボタンの入力バインディングは機能しますが、フォーカスされていないボタンは機能しません。

4

5 に答える 5

36

InputBindings は、その動作方法により、フォーカスされていないコントロールに対して実行されません。入力バインディングのハンドラーは、フォーカスされた要素からビジュアル ツリーのルート (ウィンドウ) まで、ビジュアル ツリー内で検索されます。コントロールがフォーカスされていない場合、そのコントロールはその検索パスの一部にはなりません。

@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>
于 2014-05-02T16:03:17.753 に答える
4

はい、UserControl KeyBindings は、コントロールにフォーカスがある場合にのみ機能します。

KeyBinding をウィンドウで機能させたい場合は、ウィンドウ自体で定義する必要があります。以下を使用して、Windows XAML でこれを行います。

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

ただし、UserControl で KeyBinding を定義する必要があると述べました。XAML でこれを行う方法がわからないため、UserControl のコード ビハインドでこれを設定する必要があります。つまり、UserControl の親 Window を見つけて 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 固有のロジックをビューのコード ビハインドに配置することはまったく問題ありません。

于 2014-05-02T15:14:29.020 に答える