将视图模型中的命令绑定到键盘快捷键
Bind command in view model to keyboard shortcut
我正在使用 C#、WPF、ReactiveUI 和 Prism 创建具有许多不同视图(用户控件)的应用程序。在某些视图中,有 buttons/menu 项绑定到视图模型中的命令。我希望这些按钮也可以使用组合键激活,例如 ctrl+s 等....
我试过的
InputBindings
但这仅在定义这些输入绑定的视图具有焦点时有效。
ApplicationCommands
像 ApplicationCommands.Close
这样的预定义命令似乎很有用。我可以在视图和视图模型中引用它们,但我不知道如何在我的视图模型中订阅它们。似乎我必须先 'activate' 命令,或者至少更改 CanExecute,因为绑定到此类命令的任何按钮都处于禁用状态。
我想要的
假设我有一个视图,它代表带有按钮 myButton
的顶部菜单栏 MenuView
和带有命令 myCommand
的相应视图模型 MenuViewModel
。我想将 myButton
绑定到 myCommand
并将键盘快捷键 ctrl+u
绑定到 myCommand
而 MenuView
不知道其视图模型的实现。只要包含 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
我正在使用 C#、WPF、ReactiveUI 和 Prism 创建具有许多不同视图(用户控件)的应用程序。在某些视图中,有 buttons/menu 项绑定到视图模型中的命令。我希望这些按钮也可以使用组合键激活,例如 ctrl+s 等....
我试过的
InputBindings
但这仅在定义这些输入绑定的视图具有焦点时有效。ApplicationCommands
像ApplicationCommands.Close
这样的预定义命令似乎很有用。我可以在视图和视图模型中引用它们,但我不知道如何在我的视图模型中订阅它们。似乎我必须先 'activate' 命令,或者至少更改 CanExecute,因为绑定到此类命令的任何按钮都处于禁用状态。
我想要的
假设我有一个视图,它代表带有按钮 myButton
的顶部菜单栏 MenuView
和带有命令 myCommand
的相应视图模型 MenuViewModel
。我想将 myButton
绑定到 myCommand
并将键盘快捷键 ctrl+u
绑定到 myCommand
而 MenuView
不知道其视图模型的实现。只要包含 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