将视图模型中的命令绑定到键盘快捷键

Bind command in view model to keyboard shortcut

我正在使用 C#、WPF、ReactiveUI 和 Prism 创建具有许多不同视图(用户控件)的应用程序。在某些视图中,有 buttons/menu 项绑定到视图模型中的命令。我希望这些按钮也可以使用组合键激活,例如 ctrl+s 等....

我试过的

我想要的

假设我有一个视图,它代表带有按钮 myButton 的顶部菜单栏 MenuView 和带有命令 myCommand 的相应视图模型 MenuViewModel。我想将 myButton 绑定到 myCommand 并将键盘快捷键 ctrl+u 绑定到 myCommandMenuView 不知道其视图模型的实现。只要包含 MenuView 的 window 有焦点,键盘快捷键就应该起作用。

我真的不在乎键盘快捷键是在视图中还是在视图模型中。

您可以创建附加的 Blend 行为来处理父 window 的 PreviewKeyDown 事件:

public class KeyboardShortcutBehavior : Behavior<FrameworkElement>
{
    private Window _parentWindow;

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(nameof(Command), typeof(ICommand),
        typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(null));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public static readonly DependencyProperty ModifierKeyProperty =
        DependencyProperty.Register(nameof(ModifierKey), typeof(ModifierKeys),
        typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(ModifierKeys.None));

    public ModifierKeys ModifierKey
    {
        get { return (ModifierKeys)GetValue(ModifierKeyProperty); }
        set { SetValue(ModifierKeyProperty, value); }
    }

    public static readonly DependencyProperty KeyProperty =
        DependencyProperty.Register(nameof(Key), typeof(Key),
            typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(Key.None));

    public Key Key
    {
        get { return (Key)GetValue(KeyProperty); }
        set { SetValue(KeyProperty, value); }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += AssociatedObject_Loaded;
        AssociatedObject.Unloaded += AssociatedObject_Unloaded;
    }


    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        _parentWindow = Window.GetWindow(AssociatedObject);
        if(_parentWindow != null)
        {
            _parentWindow.PreviewKeyDown += ParentWindow_PreviewKeyDown;
        }
    }

    private void ParentWindow_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if(Command != null && ModifierKey != ModifierKeys.None && Key != Key.None && Keyboard.Modifiers == ModifierKey && e.Key == Key)
            Command.Execute(null);
    }

    private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
    {
        if(_parentWindow != null)
        {
            _parentWindow.PreviewKeyDown -= ParentWindow_PreviewKeyDown;
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.Loaded -= AssociatedObject_Loaded;
        AssociatedObject.Unloaded -= AssociatedObject_Loaded;
    }
}

示例用法:

<TextBox xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
    <i:Interaction.Behaviors>
        <local:KeyboardShortcutBehavior ModifierKey="Ctrl" Key="U" Command="{Binding myCommand}" />
    </i:Interaction.Behaviors>
</TextBox>

在代码背后很容易。创建一些实用函数,最终导致父 window 键事件的可观察。请注意,您将需要 ReactiveUI.Events 库。

一些用于处理控件加载和卸载的实用程序。

    public static void LoadUnloadHandler
       ( this FrameworkElement control
       , Func<IDisposable> action
       )
    {
        var state = false;
        var cleanup = new SerialDisposable();
        Observable.Merge
            (Observable.Return(control.IsLoaded)
                , control.Events().Loaded.Select(x => true)
                , control.Events().Unloaded.Select(x => false)
            )
            .Subscribe(isLoadEvent =>
            {
                if (!state)
                {
                    // unloaded state
                    if (isLoadEvent)
                    {
                        state = true;
                        cleanup.Disposable = new CompositeDisposable(action());
                    }
                }
                else
                {
                    // loaded state
                    if (!isLoadEvent)
                    {
                        state = false;
                        cleanup.Disposable = Disposable.Empty;
                    }
                }

            });
    }

    public static IObservable<T> LoadUnloadHandler<T>(this FrameworkElement control, Func<IObservable<T>> generator)
    {
        Subject<T> subject = new Subject<T>();
        control.LoadUnloadHandler(() => generator().Subscribe(v => subject.OnNext(v)));
        return subject;
    }

还有一个专门用于处理已加载控件的 window

    public static IObservable<T> LoadUnloadHandler<T>
        (this FrameworkElement control, Func<Window, IObservable<T>> generator)
    {
        Subject<T> subject = new Subject<T>();
        control.LoadUnloadHandler(() => generator(Window.GetWindow(control)).Subscribe(v => subject.OnNext(v)));
        return subject;
    }

最后是任何控件window的父级的键处理程序

    public static IObservable<KeyEventArgs> ParentWindowKeyEventObservable(this FrameworkElement control)
        => control.LoadUnloadHandler((Window window) => window.Events().PreviewKeyDown);

现在你可以做

  Button b;
  b.ParentWindowKeyEventObservable()
   .Subscribe( kEvent => {

        myCommand.Execute();
   }

它可能看起来有点复杂,但我在大多数用户控件上使用 LoadUnloadHandler 来随着 UI 生命周期的进行获取和处理资源。

您想为此使用 KeyBindings。这允许您将键盘组合键绑定到命令。在此处阅读文档:https://msdn.microsoft.com/en-us/library/system.windows.input.keybinding(v=vs.110).aspx