深入浅出WPF命令系统之InputBinding(MouseBinding与KeyBinding)
之前的随笔中剖析了WPF命令系统的一部分,文中是通过CommandBinding和Binding两种方式将命令(ICommand)与Button控件进行关联,如下:
方式一. CommandBinding
1 <Window.Resources> 2 <RoutedCommand x:Key="MyTestCommand"/> 3 </Window.Resources> 4 <Window.CommandBindings> 5 <CommandBinding Command="{StaticResource MyTestCommand}" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute"/> 6 </Window.CommandBindings> 7 8 <Grid> 9 <Button Height="100" 10 Width="200" 11 Content="click me" 12 Command="{StaticResource MyTestCommand}"/> 13 </Grid>
方式二. Binding(即绑定数据源中的实现了ICommand接口的对象),如
1 <Grid> 2 <Button Height="100" 3 Width="200" 4 Content="click me" 5 Command="{Binding TestCommand}"/> 6 </Grid>
其中,TestCommand是DataContext中的一个实现了ICommand接口的对象。
上述两种方式,点击Button控件后会执行绑定的命令。但实际开发场景中,如果遇到了Button以外的需求,比如需要通过键盘或者鼠标来触发命令,该怎么办?
对,我们可以通过UIElement中的InputBindings属性来实现。
先来看看具体怎么用吧。
需求1.按下键盘上的指定键时(假设是Ctrl + N)处理某个事情(弹出“Hello world”对话框)
实现如下:
1 <Window.InputBindings> 2 <KeyBinding Command="{Binding KeyCommand}" Gesture="Ctrl+N"/> 3 </Window.InputBindings> 4 5 public partial class MainWindow : Window 6 { 7 public MyCommand KeyCommand { get; } 8 9 public MainWindow() 10 { 11 InitializeComponent(); 12 DataContext = this; 13 KeyCommand = new MyCommand(OnKeyCommandExecuted); 14 } 15 16 private void OnKeyCommandExecuted(object parameter) 17 { 18 MessageBox.Show("Hello world"); 19 } 20 } 21 22 public class MyCommand : ICommand 23 { 24 private readonly Action<object> _executeMethod; 25 26 public event EventHandler CanExecuteChanged; 27 28 public MyCommand(Action<object> executeMethod) 29 { 30 _executeMethod = executeMethod; 31 } 32 33 public bool CanExecute(object parameter) 34 { 35 return true; 36 } 37 38 public void Execute(object parameter) 39 { 40 _executeMethod?.Invoke(parameter); 41 } 42 }
需求2.操作鼠标上的按键(假设右键双击)后处理某些事情(弹出“你好,世界”对话框)
实现如下:
1 <Window.InputBindings> 2 <MouseBinding Command="{Binding MouseCommand}" MouseAction="RightDoubleClick"/> 3 </Window.InputBindings> 4 5 public partial class MainWindow : Window 6 { 7 public MyCommand MouseCommand { get; } 8 9 public MainWindow() 10 { 11 InitializeComponent(); 12 13 DataContext = this; 14 15 MouseCommand = new MyCommand(OnMouseCommandExecuted); 16 } 17 18 private void OnMouseCommandExecuted(object parameter) 19 { 20 MessageBox.Show("你好,世界"); 21 } 22 }
上述两个例子中分别使用了KeyBinding和MouseBinding,它们都是InputBinding的派生类,而UIElement.InputBindings集合的元素类型正是InputBinding。
那KeyBinding和MouseBinding到底是怎么回事呢?请看下文。
KeyBinding
KeyBinding的源码比较简单(感兴趣的小伙伴自行翻看,这里就不展示了),KeyBinding的功能就是将键盘按键和命令关联,所以KeyBinding中关键的元素就是它的Command、Gesture、ModifierKey和Key属性。.net提供了Gesture(KeyGesture)和Modifiers+Key两种方式来关联Command,这两种方式的效果是一样的,但以下几种使用方式是错误的:
- Gesture和ModifierKey+Key共用,比如既指定了Gesture又指定了Key;
- Gesture仅指定了Key,没有指定ModifierKey。这种方式编译时没问题,但运行时直接报错。
- 仅指定ModifierKey,未指定Key;
MS推荐在XAML中使用Gesture(KeyGesture)这种方式,但遗憾的是,KeyGesture还不支持None+Key这种方式,即使用KeyGesture时,必须指定修饰键。
Gesture(KeyGesture)在XAML中的字符串形式是“ModifierKey+Key”,如“Ctrl+N”。.net会通过KeyGestureConverter类型转换器来解析此字符串。
MouseBinding
与KeyBinding类似,MouseBinding的功能是将鼠标操作与命令进行关联。MouseBinding也提供了两种方式来关联命令,它们是Gesture(MouseGesture)和MouseAction(枚举类型),这两种方式也是等效的。在这两种方式共用的情况下,运行时也不会报错,但仅最后声明的方式有效,比如
<MouseBinding Command="{Binding MouseCommand}" Gesture="LeftClick" MouseAction="RightDoubleClick"/>
这时,仅RightDoubleClick有效。
对于MouseBinding,微软也推荐在XAML中使用Gesture。
Gesture(MouseGesture)在XAML中的字符串形式是“ModifierKey+MouseAction”,如“Ctrl+LeftClick”,且可以不指定ModifierKey。
.net会通过MouseGestureConverter类型转换器来解析此字符串。
UIElement中的InputBindings是如何被执行的?
用反编译工具查了一下,并没有发现鼠标或键盘动作时调用InputBindings的相关代码,但是我们看到在UIElement中,InputBindings是这样实现的:
1 public InputBindingCollection InputBindings 2 { 3 get 4 { 5 VerifyAccess(); 6 InputBindingCollection inputBindingCollection = InputBindingCollectionField.GetValue(this); 7 if (inputBindingCollection == null) 8 { 9 inputBindingCollection = new InputBindingCollection(this); 10 InputBindingCollectionField.SetValue(this, inputBindingCollection); 11 } 12 13 return inputBindingCollection; 14 } 15 }
通过上述代码可以发现,InputBindings实际是对另一个字段InputBindingCollectionField的包装,而在InputBindings属性不远的地方,还有一个属性InputBindingsInternal,它是这样的:
1 internal InputBindingCollection InputBindingsInternal 2 { 3 get 4 { 5 VerifyAccess(); 6 return InputBindingCollectionField.GetValue(this); 7 } 8 }
这两个属性不就是同一个东西嘛,既然如此,不妨找找这个属性,果然看到有人在用它:
而与OnMouseDownThunk、OnMouseWheelThunk和OnKeyDownThunk关联的事件分别是Mouse.MouseDownEvent,Mouse.MouseWheelEvent和Keyboard.KeyDownEvent。
OK,打完收工。
本文来自博客园,作者:叶落劲秋,转载请注明原文链接:https://www.cnblogs.com/tianlang358/p/18798523