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

时间:2017-03-23 09:37:53

标签: c# wpf prism reactiveui

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

我尝试了什么

  • InputBindings但只有在定义这些输入绑定的视图具有焦点时才有效。
  • ApplicationCommands预定义命令(如ApplicationCommands.Close)似乎很有用。我可以在视图和视图模型中引用它们,但我不知道如何在视图模型中订阅它们。似乎我必须先“激活”命令,或者至少更改CanExecute,因为任何绑定到此命令的按钮都会被禁用。

我希望

假设我的视图代表顶部菜单栏MenuView带有按钮myButton,相应的视图模型MenuViewModel带有命令myCommand。我想将myButton绑定到myCommand,将键盘快捷键ctrl+u绑定到myCommand,而MenuView不知道其视图模型的实现。只要包含MenuView的窗口具有焦点,键盘快捷键就可以正常工作。

我并不关心键盘快捷键是在视图模型还是视图模型中。

3 个答案:

答案 0 :(得分:1)

您可以创建一个附加的Blend行为来处理父窗口的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>

答案 1 :(得分:0)

在代码背后容易。创建一些实用程序函数,最终导致父窗口键事件的可观察。请注意,您将需要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;
    }

和一个专门用于处理加载控件的窗口

    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;
    }

最后是任何控件的父窗口的键处理程序

    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生命周期的进展获取和处理资源。

答案 2 :(得分:0)

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