在代码后面将Command绑定到KeyBinding

本文关键字:绑定 KeyBinding Command 代码 | 更新日期: 2023-09-27 18:12:45

我想在我的应用程序中添加一些通用的键盘快捷键。目前,在每个View XAML中,我添加了以下代码:

<Window.InputBindings>
    <KeyBinding Command="{Binding ZoomInCommand}" Key="Add" Modifiers="Control" />
    <KeyBinding Command="{Binding ZoomOutCommand}" Key="Subtract" Modifiers="Control" />
</Window.InputBindings>

为了推广这一点,我想子类化WPF窗口类并使用新创建的子类代替。现在我想知道如何在相应的代码中绑定这些键盘命令。目前看起来是这样的:

public class MyWindow : Window
{
    public MyWindow()
    {
        DataContextChanged += OnDataContextChanged;
    }
    private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        InputBindings.Clear();
        var dataContext = DataContext as IZoomableViewModel;
        if (dataContext != null)
        {
            InputBindings.Add(new KeyBinding(dataContext.ZoomInCommand, Key.Add, ModifierKeys.Control));
            InputBindings.Add(new KeyBinding(dataContext.ZoomOutCommand, Key.Subtract, ModifierKeys.Control));
        }
    }
}

但是这看起来不对,因为我需要直接访问DataContext并强制转换它,而不是使用Binding()对象。如何更改代码使其看起来更像mvvm ?

在代码后面将Command绑定到KeyBinding

我发现了一个简单的解决方案,它工作得很好,似乎模仿了XAML解析器的行为。基本上,对于放大功能,我将以下代码放入MyWindow构造函数中:

var zoomInKeyBinding = new KeyBinding { Key = Key.Add, Modifiers = ModifierKeys.Control };
BindingOperations.SetBinding(
    zoomInKeyBinding,
    InputBinding.CommandProperty,
    new Binding { Path = new PropertyPath("ZoomInCommand") }
);
InputBindings.Add(zoomInKeyBinding);

当然,绑定的ViewModel需要适当地实现ZoomInCommand。

你需要的是依赖属性

MyWindow中,为命令创建一个ICommand依赖属性,当依赖属性值发生变化时,您还需要实现一个回调方法,这是ZoomInCommand的一个:

public ICommand ZoomInCommand
{
    get { return (ICommand)GetValue(ZoomInCommandProperty); }
    set { SetValue(ZoomInCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for ZoomInCommand.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty ZoomInCommandProperty =
    DependencyProperty.Register("ZoomInCommand", typeof(ICommand), typeof(MyWindow), new PropertyMetadata(null, new PropertyChangedCallback(OnZoomInCommandChanged)));
...
private static void OnZoomInCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    MyWindow wnd = (MyWindow)d;
    //Remove the old key binding if there is one.
    wnd.RemoveInputBinding(e.OldValue as ICommand);
    //Add the new input binding.
    if (e.NewValue != null)
        wnd.InputBindings.Add(new KeyBinding((ICommand)e.NewValue, Key.Add, ModifierKeys.Control));
}
private void RemoveInputBinding(ICommand command)
{
    if (command == null)
        return;
    //Find the old binding if there is one.
    InputBinding oldBinding = null;
    foreach (InputBinding binding in InputBindings)
    {
        if (binding.Command == command)
        {
            oldBinding = binding;
            break;
        }
    }
    //Remove the old input binding.
    if (oldBinding != null)
        InputBindings.Remove(oldBinding);
}

那么上面的代码到底是做什么的呢?

在依赖属性上,有一个PropertyChangedCallback方法是可选的,它将在属性值改变时触发,这很好,因为我们可以使用它来删除旧的InputBinding,并在值改变时创建一个新的InputBinding。在您的示例中,当DataContext更改时,该值将更改。

所以步骤很简单,当属性值改变时:

  1. 为旧ICommand删除旧InputCommand
  2. 为新的ICommand添加新的InputCommand

我已经创建了一个方便的RemoveInputBinding方法,它应该可以更容易地为您的其他依赖属性重用代码,这取决于您的实现。


为了适应这些,在您的DataContextChanged事件处理程序中,您只需要编写一个手动绑定:

private void MyWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    //Bind this.ZoomInCommand to DataContext.ZoomInCommand
    Binding zoomInCommandBinding = new Binding("ZoomInCommand");
    zoomInCommandBinding.Source = DataContext;
    this.SetBinding(MyWindow.ZoomInCommandProperty, zoomInCommandBinding);
    ...
}

这将确保您不再需要担心将DataContext转换为IZoomableViewModel,您只需尝试绑定到ZoomInCommand。如果DataContext中没有这样的命令,那么它将默默地失败。但是,如果成功,则会触发PropertyChangedCallback,并为该命令创建一个InputBinding