WPF中的键盘快捷键

140

我知道在使用下划线_代替&,但我想了解所有的Ctrl+键盘按键快捷方式。

Ctrl+Z 为撤销,Ctrl+S 为保存,等等。

在WPF应用程序中,是否有一种“标准”方法来实现这些快捷键?还是需要自己编写代码并将其连接到相应的命令/控件?

11个回答

182

一种方法是将快捷键添加到命令本身中作为 InputGestures。 命令实现为RoutedCommands

这使得即使没有连接到任何控件,快捷键也能起作用。而且由于菜单项可以理解键盘手势,如果将该命令连接到菜单项,则菜单项将自动在菜单项文本中显示您的快捷键。


步骤

  1. 创建静态属性来保存命令(最好作为你为命令创建的静态类中的属性,但为了简单起见,只需在window.cs中使用静态属性):

  2.  public static RoutedCommand MyCommand = new RoutedCommand();
    
  3. 添加应该调用方法的快捷键:

  4.  MyCommand.InputGestures.Add(new KeyGesture(Key.S, ModifierKeys.Control));
    
  5. 创建一个命令绑定,将其指向要在执行时调用的方法。将它们放置在应该工作的UI元素的命令绑定下面(例如,窗口)和方法中:

  6.  <Window.CommandBindings>
         <CommandBinding Command="{x:Static local:MyWindow.MyCommand}" 
                         Executed="MyCommandExecuted"/>
     </Window.CommandBindings>
    
     private void MyCommandExecuted(object sender, ExecutedRoutedEventArgs e) 
     { ... }
    

4
我如何将命令与菜单项关联?这肯定是需要包含在答案中最重要的信息,但现在却没有提到。 - Timwi
8
@Timwi:我以以下方式使用上述代码来为现有事件添加键盘快捷键:RoutedCommand cmndSettings = new RoutedCommand(); cmndSettings.InputGestures.Add(new KeyGesture(Key.S, ModifierKeys.Control)); CommandBindings.Add(new CommandBinding(cmndSettings, mnuSettings_Click)); - itsho
1
itsho的评论让我成功了,之前无法让上面的XML代码工作。 - gosr
2
不幸的是,使用这种方法,命令的“已执行”代码最终会出现在代码后端(窗口或用户控件)中,而不是视图模型中,与使用常规命令(自定义“ICommand”实现)相反。 - O. R. Mapper
2
类似示例 http://www.wpf-tutorial.com/commands/implementing-custom-commands/ - Narottam Goyal
显示剩余2条评论

112

我发现这是与WPF中键绑定相关的完美解决方案:

<Window.InputBindings>
        <KeyBinding Modifiers="Control"
                    Key="N"
                    Command="{Binding CreateCustomerCommand}" />
</Window.InputBindings>

查看博客文章MVVM CommandReference 和 KeyBinding


非常好和容易! - merger
2
您介意详细说明一下“CreateCustomerCommand”是什么以及如何实现吗? - Vinz
这仍然是一个链接答案,因为复制粘贴的代码片段在链接的博客文章中被描述为“结果将是异常”。 :P - Martin Schneider
这里的效果非常好。我首先尝试像 OP 一样在按钮内容的键前添加“_”,但它没有起作用。更糟糕的是,当我不在界面的可写对象上时,它会在我按下键本身时激活,比如“s”表示保存,而不是 Ctrl-S。 - Jay
5
对于多个修饰符,请使用"+"号,例如 "Modifiers =“Control+Alt”"。请参考https://dev59.com/B2855IYBdhLWcg3w_5cQ。 - Pang

22

试一下这个。

首先创建一个RoutedCommand对象:

RoutedCommand newCmd = new RoutedCommand();
newCmd.InputGestures.Add(new KeyGesture(Key.N, ModifierKeys.Control));
CommandBindings.Add(new CommandBinding(newCmd, btnNew_Click));

这就是正确的方法!感谢Shahid!绕过所有的WPF麻烦,以编程方式完成它。 - A. Vieira
似乎WPF已经尽可能地把这个简单的任务变得复杂了。我试着去尝试其他建议,但是发现它们都不能正常工作,让我自己陷入了疯狂之中。需要注意的一点是:如果你正在为一个菜单项设置快捷键,并且想要在菜单中显示快捷键,你仍然需要单独配置: <MenuItem x:Name="loadSheetCommand" Header="Load From _Spreadsheet" Click="loadSheetCommand_Click" InputGestureText="Ctrl+O" />但这是我目前找到的最简单的解决方案,而且它有效! - Stewart

9

这取决于您想在哪里使用它们。

TextBoxBase派生控件已经实现了这些快捷方式。如果您想使用自定义键盘快捷键,您应该查看命令和输入手势。这是来自Switch on the Code的一个小教程:WPF 教程 - 命令绑定和自定义命令


8
这个教程真是太差劲了——它没有解释最重要的一点,即如何使用一个不属于他们预定义的20个“常见”命令之一的命令。 - Timwi

6
为了方便其他人,我把这个答案记录下来。有一个更简单的方法可以做到这一点,很少有人提到它,而且根本不需要触摸XAML。
要关联一个键盘快捷键,在Window构造函数中,只需将一个新的KeyBinding添加到InputBindings集合中。作为命令,传递实现ICommand的任意命令类。对于执行方法,只需实现所需的逻辑即可。在我的示例中,我的WindowCommand类接受一个委托,每次调用时都会执行。当我构造新的WindowCommand以与我的绑定一起传递时,我只需在初始化器中指示我想要WindowCommand执行的方法。
您可以使用此模式来创建自己的快速键盘快捷方式。
public YourWindow() //inside any WPF Window constructor
{
   ...
   //add this one statement to bind a new keyboard command shortcut
   InputBindings.Add(new KeyBinding( //add a new key-binding, and pass in your command object instance which contains the Execute method which WPF will execute
      new WindowCommand(this)
      {
         ExecuteDelegate = TogglePause //REPLACE TogglePause with your method delegate
      }, new KeyGesture(Key.P, ModifierKeys.Control)));
   ...
}

创建一个简单的WindowCommand类,它采用执行委托来触发在其上设置的任何方法。
public class WindowCommand : ICommand
{
    private MainWindow _window;

    //Set this delegate when you initialize a new object. This is the method the command will execute. You can also change this delegate type if you need to.
    public Action ExecuteDelegate { get; set; }

    //You don't have to add a parameter that takes a constructor. I've just added one in case I need access to the window directly.
    public WindowCommand(MainWindow window)
    {
        _window = window;
    }

    //always called before executing the command, mine just always returns true
    public bool CanExecute(object parameter)
    {
        return true; //mine always returns true, yours can use a new CanExecute delegate, or add custom logic to this method instead.
    }

    public event EventHandler CanExecuteChanged; //i'm not using this, but it's required by the interface

    //the important method that executes the actual command logic
    public void Execute(object parameter)
    {
        if (ExecuteDelegate != null)
        {
            ExecuteDelegate();
        }
        else
        {
            throw new InvalidOperationException();
        }
    }
}

5

我曾遇到类似的问题,发现@aliwa的回答最有帮助且解决方案最优雅;然而,我需要一个特定的按键组合:Ctrl+1。不幸的是,我遇到了以下错误:

'1'不能用作“Key”的值。数字不是有效的枚举值。

经过进一步搜索,我修改了@aliwa的答案如下:

<Window.InputBindings>
    <KeyBinding Gesture="Ctrl+1" Command="{Binding MyCommand}"/>
</Window.InputBindings>

我发现这对我需要的任何组合都非常有效。

这段代码对我来说可行:<UserControl.InputBindings> <KeyBinding Gesture="Enter" Command="{Binding someCommand}"/> </UserControl.InputBindings> - fs_tigre

3

VB.NET:

Public Shared SaveCommand_AltS As New RoutedCommand

loaded事件中:

SaveCommand_AltS.InputGestures.Add(New KeyGesture(Key.S, ModifierKeys.Control))

Me.CommandBindings.Add(New CommandBinding(SaveCommand_AltS, AddressOf Me.save))

不需要使用XAML。

1

我尝试了各种使用XAML的方法,但都没有成功。最终,我找到了一个基于Shahid Neermunda提供的答案的解决方案。

首先是菜单栏:

<Menu x:Name="MainMenuBar" Grid.Row="0" HorizontalContentAlignment="Left">
    <MenuItem Header="_File" HorizontalContentAlignment="Left">
        <MenuItem x:Name="NewProjectMenuItem"
                    Header="New Project"
                    InputGestureText="Ctrl+N"
                    Click="NewProject_Click"/>
        <MenuItem x:Name="OpenProjectMenuItem"
                    Header="Open Project"
                    InputGestureText="Ctrl+O"
                    Click="OpenProject_Click"/>
        <MenuItem x:Name="CloseProjectMenuItem"
                    Header="Close Project"
                    Click="CloseProject_Click"/>
        <Separator/>
        <MenuItem x:Name="SaveProjectMenuItem"
                    Header="Save Project"
                    InputGestureText="Ctrl+S"
                    Click="SaveProject_Click"/>
        <MenuItem x:Name="SaveProjectAsMenuItem"
                    Header="Save Project As ..."
                    InputGestureText="Shift+Ctrl+S"
                    Click="SaveProjectAs_Click"/>
        <Separator/>
        <MenuItem x:Name="ExitMenuItem"
                    Header="Exit"
                    InputGestureText="Alt+F4"
                    Click="Exit_Click"/>
    </MenuItem>
</Menu>

没什么花哨的。每个菜单项都有一个“InputGestureText”属性(除了关闭)

然后我修改了由Click =“[tab]”命令自动生成的单击事件方法。我只显示了两个——一个定义了快捷键,另一个没有(关闭):

private void OpenProject_Executed(object sender, ExecutedRoutedEventArgs e) => OpenProject_Click(sender, e);
private void OpenProject_Click(object sender, RoutedEventArgs e)
{
    OpenProject();
}

private void CloseProject_Click(object sender, RoutedEventArgs e)
{
    CloseProject();
}

XXX_Executed(...)方法由快捷键绑定调用(我接下来会说到),而XXX_Click方法由Click命令调用。

对于New Project、Open Project、Save Project As和Exit自动生成的XXX_Click方法,我也做了同样的处理。

然后,我创建了一个带有绑定的新文件(我将其分开以便在添加其他绑定时更容易找到):

partial class MainWindow
{
    private void BindShortcuts()
    {
        BindShortcut(Key.N, ModifierKeys.Control, NewProject_Executed);
        BindShortcut(Key.O, ModifierKeys.Control, OpenProject_Executed);
        BindShortcut(Key.S, ModifierKeys.Control, SaveProject_Executed);
        BindShortcut(Key.S, ModifierKeys.Control | ModifierKeys.Shift, SaveProjectAs_Executed);
        BindShortcut(Key.F4, ModifierKeys.Alt, Exit_Executed);
    }

    private void BindShortcut(Key key, ModifierKeys modifiers, ExecutedRoutedEventHandler executed)
    {
        RoutedCommand cmd = new();
        _ = cmd.InputGestures.Add(new KeyGesture(key, modifiers));
        _ = CommandBindings.Add(new CommandBinding(cmd, executed));
    }
}

这样,当我添加新的菜单项并附加更多快捷方式时,我只需要添加相应的<MenuItem ... />标记,定义一个XXX_Executed方法来调用自动生成的XXX_Click方法,并更新BindShortcuts()函数。
最后,我在MainWindow类的构造函数中添加了以下内容:
public MainWindow()
{
    InitializeComponent();
    BindShortcuts();
}

像魔法一样运作。


1
尽管前面的答案是正确的,但我个人更喜欢使用附加属性来实现解决方案适用于任何UIElement,特别是当Window不知道应该聚焦哪个元素时。根据我的经验,我经常看到几个视图模型和用户控件的组合,其中窗口通常只是根容器。

片段

public sealed class AttachedProperties
{
    // Define the key gesture type converter
    [System.ComponentModel.TypeConverter(typeof(System.Windows.Input.KeyGestureConverter))]
    public static KeyGesture GetFocusShortcut(DependencyObject dependencyObject)
    {
        return (KeyGesture)dependencyObject?.GetValue(FocusShortcutProperty);
    }

    public static void SetFocusShortcut(DependencyObject dependencyObject, KeyGesture value)
    {
        dependencyObject?.SetValue(FocusShortcutProperty, value);
    }

    /// <summary>
    /// Enables window-wide focus shortcut for an <see cref="UIElement"/>.
    /// </summary>
    // Using a DependencyProperty as the backing store for FocusShortcut.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FocusShortcutProperty =
        DependencyProperty.RegisterAttached("FocusShortcut", typeof(KeyGesture), typeof(AttachedProperties), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnFocusShortcutChanged)));

    private static void OnFocusShortcutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is UIElement element) || e.NewValue == e.OldValue)
            return;

        var window = FindParentWindow(d);
        if (window == null)
            return;

        var gesture = GetFocusShortcut(d);
        if (gesture == null)
        {
            // Remove previous added input binding.
            for (int i = 0; i < window.InputBindings.Count; i++)
            {
                if (window.InputBindings[i].Gesture == e.OldValue && window.InputBindings[i].Command is FocusElementCommand)
                    window.InputBindings.RemoveAt(i--);
            }
        }
        else
        {
            // Add new input binding with the dedicated FocusElementCommand.
            // see: https://gist.github.com/shuebner20/349d044ed5236a7f2568cb17f3ed713d
            var command = new FocusElementCommand(element);
            window.InputBindings.Add(new InputBinding(command, gesture));
        }
    }
}

使用这个附加属性,你可以为任何UIElement定义一个焦点快捷方式。它会自动在包含该元素的窗口中注册输入绑定。

用法(XAML)

<TextBox x:Name="SearchTextBox"
         Text={Binding Path=SearchText}
         local:AttachedProperties.FocusShortcutKey="Ctrl+Q"/>

源代码

包括FocusElementCommand实现的完整示例可在gist上找到: https://gist.github.com/shuebner20/c6a5191be23da549d5004ee56bcc352d

免责声明:您可以在任何地方免费使用此代码。请注意,这只是一个示例,不适用于重度使用。例如,由于命令将保持对元素的强引用,因此不会对已删除的元素进行垃圾回收。


0

特殊情况:如果焦点在“非本地”元素上,则您的快捷方式不会触发。例如,在我的情况下,聚焦于WpfCurrencyTextbox将不会触发在XAML中定义的快捷方式(像oliwa的答案中所定义的那样)。

我通过使用NHotkey包使我的快捷方式全局化来解决此问题。

简而言之,对于XAML,您只需要替换

<KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}" />

<KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}"
            HotkeyManager.RegisterGlobalHotkey="True" />

答案也已发布到:如何使用WPF和.NET 3.5注册全局热键以按CTRL+SHIFT+(LETTER)?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接