深入浅出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,这两种方式的效果是一样的,但以下几种使用方式是错误的:

  1. Gesture和ModifierKey+Key共用,比如既指定了Gesture又指定了Key;
  2. Gesture仅指定了Key,没有指定ModifierKey。这种方式编译时没问题,但运行时直接报错。
  3. 仅指定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,打完收工。

posted @ 2025-03-28 17:46  叶落劲秋  阅读(139)  评论(0)    收藏  举报