Keyboard shortcuts in WPF
Asked Answered
C

11

143

I know about using _ instead of &, but I'm looking at all the Ctrl + type shortcuts.

Ctrl+Z for undo, Ctrl+S for save, etc.

Is there a 'standard' way for implementing these in WPF applications? Or is it a case of roll your own and wire them up to whatever command/control?

Camara answered 1/9, 2009 at 8:58 Comment(0)
P
184

One way is to add your shortcut keys to the commands themselves them as InputGestures. Commands are implemented as RoutedCommands.

This enables the shortcut keys to work even if they're not hooked up to any controls. And since menu items understand keyboard gestures, they'll automatically display your shortcut key in the menu items text, if you hook that command up to your menu item.


Steps

  1. Create static attribute to hold a command (preferably as a property in a static class you create for commands - but for a simple example, just using a static attribute in window.cs):

     public static RoutedCommand MyCommand = new RoutedCommand();
    
  2. Add the shortcut key(s) that should invoke method:

     MyCommand.InputGestures.Add(new KeyGesture(Key.S, ModifierKeys.Control));
    
  3. Create a command binding that points to your method to call on execute. Put these in the command bindings for the UI element under which it should work for (e.g., the window) and the method:

     <Window.CommandBindings>
         <CommandBinding Command="{x:Static local:MyWindow.MyCommand}" 
                         Executed="MyCommandExecuted"/>
     </Window.CommandBindings>
    
     private void MyCommandExecuted(object sender, ExecutedRoutedEventArgs e) 
     { ... }
    
Prolate answered 25/9, 2009 at 19:0 Comment(7)
How do I associate the command with a menu item? Surely that would be the most important information to include in this answer, but it’s missing.Seger
@Timwi: i've used the code above in this way to add keyboard shortcut to an existing event: RoutedCommand cmndSettings = new RoutedCommand(); cmndSettings.InputGestures.Add(new KeyGesture(Key.S, ModifierKeys.Control)); CommandBindings.Add(new CommandBinding(cmndSettings, mnuSettings_Click));Armorial
itsho's comment made this work for me, couldn't make the xml code above work.Twine
Unfortunately, with this approach, the Executed code for the command will end up in the code-behind (of the window or user-control) rather than the view-model, as opposed to using a usual command (custom ICommand implementation).Rope
Similar Example wpf-tutorial.com/commands/implementing-custom-commandsLaundromat
To add this command to a button or menuitem, use <Button Command=":local:MyWindow.MyCommand" ... />Lapith
I'm getting this error The member "MyCommand" is not recognized or is not accessibleSerried
B
114

I found this to be exactly what I was looking for related to key binding in WPF:

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

See blog post MVVM CommandReference and KeyBinding

Berber answered 20/5, 2011 at 18:57 Comment(5)
Very nice and easy!Merline
Would you mind elaborating on what "CreateCustomerCommand" is and how it is to be implemented?Jarvis
This is still a link only answer since the copy&pasted code snippet is described with "The result will be an Exception" on the linked blog post. :PAgamemnon
Works marvel here. I first tried adding "_" before the key of the button content, like the OP, but it did not work. Worst, it activated when I hit the key itself when I was not on focus into a writable object of the interface.. like "s" for save, instead of ctrl-s.Scroll
For multiple modifiers, use +, e.g. Modifiers="Control+Alt". See https://mcmap.net/q/161320/-creating-keybinding-in-wpf-with-more-than-one-modifier-keyLeopardi
C
24

Try this.

First create a RoutedCommand object:

RoutedCommand newCmd = new RoutedCommand();
newCmd.InputGestures.Add(new KeyGesture(Key.N, ModifierKeys.Control));
CommandBindings.Add(new CommandBinding(newCmd, btnNew_Click));
Contactor answered 31/10, 2015 at 10:39 Comment(2)
This is the way to go! Thanks Shahid! Bypass all the wpf bs and do it programatically.Extrude
It does seem WPF has gone out of its way to make this simple task as complicated as possible. I was just driving myself mad trying other suggestions and finding I can't get them to work. One thing to note: If you're doing it for a MenuItem and you want the shortcut key to display in the menu, you will still need to configure this separately: <MenuItem x:Name="loadSheetCommand" Header="Load From _Spreadsheet" Click="loadSheetCommand_Click" InputGestureText="Ctrl+O" /> But this is the simplest solution I've found so far and it works!Carrel
D
9

It depends on where you want to use those.

TextBoxBase-derived controls already implement those shortcuts. If you want to use custom keyboard shortcuts you should take a look on Commands and Input gestures. Here is a small tutorial from Switch on the Code: WPF Tutorial - Command Bindings and Custom Commands

Derosier answered 1/9, 2009 at 9:12 Comment(1)
What a crap tutorial — doesn’t explain the absolutely most important thing of all, which is how to use a command that doesn’t happen to be one of their predefined set of 20 “common” commands.Seger
P
6

Documenting this answer for others, as there is a much simpler way to do this that is rarely referenced, and doesn't require touching the XAML at all.

To link a keyboard shortcut, in the Window constructor simply add a new KeyBinding to the InputBindings collection. As the command, pass in your arbitrary command class that implements ICommand. For the execute method, simply implement whatever logic you need. In my example below, my WindowCommand class takes a delegate that it will execute whenever invoked. When I construct the new WindowCommand to pass in with my binding, I simply indicate in my initializer, the method that I want the WindowCommand to execute.

You can use this pattern to come up with your own quick keyboard shortcuts.

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)));
   ...
}

Create a simple WindowCommand class which takes an execution delegate to fire off any method set on it.

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();
        }
    }
}
Popcorn answered 27/7, 2016 at 0:19 Comment(0)
L
5

I had a similar problem and found @aliwa's answer to be the most helpful and most elegant solution; however, I needed a specific key combination, Ctrl + 1. Unfortunately I got the following error:

'1' cannot be used as a value for 'Key'. Numbers are not valid enumeration values.

With a bit of further search, I modified @aliwa's answer to the following:

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

I found this to work great for pretty well any combination I needed.

Lullaby answered 2/12, 2017 at 23:19 Comment(1)
This worked for me <UserControl.InputBindings> <KeyBinding Gesture="Enter" Command="{Binding someCommand}"/> </UserControl.InputBindings>Incandesce
E
3

VB.NET:

Public Shared SaveCommand_AltS As New RoutedCommand

Inside the loaded event:

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

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

No XAML is needed.

Excrete answered 23/4, 2013 at 11:59 Comment(0)
D
1

Although the top answers are correct, I personally like to work with attached properties to enable the solution to be applied to any UIElement, especially when the Window is not aware of the element that should be focused. In my experience I often see a composition of several view models and user controls, where the window is often nothing more that the root container.

Snippet

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));
        }
    }
}

With this attached property you can define a focus shortcut for any UIElement. It will automatically register the input binding at the window containing the element.

Usage (XAML)

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

Source code

The full sample including the FocusElementCommand implementation is available as gist: https://gist.github.com/shuebner20/c6a5191be23da549d5004ee56bcc352d

Disclaimer: You may use this code everywhere and free of charge. Please keep in mind, that this is a sample that is not suitable for heavy usage. For example, there is no garbage collection of removed elements because the Command will hold a strong reference to the element.

Diffractive answered 19/8, 2019 at 15:30 Comment(0)
C
1

I tried all kinds of approaches using XAML and nothing worked. I finally found a solution based on the answer proved by Shahid Neermunda

First, the menu bar:

<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>

Nothing fancy. Each menu item has an 'InputGestureText' property (except for the close)

I then modified the click event methods that were auto-generated by the Click="[tab]" command. I'm only showing two here--one where there's a shortcut key defined and another where it isn't (Close):

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();
}

The XXX_Executed(...) method is called by the shortcut binding (which I'll get to next) and the XXX_Click method is called by the Click command.

I did the same for the New Project, Open Project, Save Project As, and Exit auto-generated XXX_Click methods.

I then created a new file with the bindings (I separated it out to make it easier to find when the time comes to add additional bindings):

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));
    }
}

This way, when I add new menu items with more shortcuts attached, I only need to add the appropriate <MenuItem .../> tag, define an XXX_Executed method to call into the auto-generated XXX_Click method, and update the BindShortcuts() function.

Finally, I added the following to my constructor for the MainWindow class:

public MainWindow()
{
    InitializeComponent();
    BindShortcuts();
}

Works like a charm.

Corrinacorrine answered 3/8, 2021 at 1:39 Comment(0)
M
0

Special case: your shortcut doesn't trigger if the focus is on an element that "isn't native". In my case for example, a focus on a WpfCurrencyTextbox won't trigger shortcuts defined in your XAML (defined like in oliwa's answer).

I fixed this issue by making my shortcut global with the NHotkey package.

In short, for XAML, all you need to do is to replace

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

by

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

Answer has also been posted to: How can I register a global hot key to say CTRL+SHIFT+(LETTER) using WPF and .NET 3.5?

Mclaurin answered 11/9, 2020 at 8:16 Comment(0)
S
-2

How to associate the command with a MenuItem:

<MenuItem Header="My command" Command="{x:Static local:MyWindow.MyCommand}"/>
Savona answered 5/2, 2014 at 10:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.