59

I need a simple way to set a shortcut for menu items.

But this don´t work with shortcut, just with click:

<MenuItem Header="Editar">
    <MenuItem Header="Procurar" Name="MenuProcurar"
              InputGestureText="Ctrl+F"
              Click="MenuProcurar_Click">
        <MenuItem.ToolTip>
            <ToolTip>
                Procurar
            </ToolTip>
        </MenuItem.ToolTip>
    </MenuItem>
</MenuItem>

I am using WPF 4.0

H.B.
  • 166,899
  • 29
  • 327
  • 400
Felipe Pessoto
  • 6,855
  • 10
  • 42
  • 73

7 Answers7

82

H.B. was right... I just wanted to add more precisions.

Remove the Click event on your MenuItem and associate it with a Command instead.

1 - Add/create your commands:

<Window.CommandBindings>
     <CommandBinding Command="Open" Executed="OpenCommandBinding_Executed"/>
     <CommandBinding Command="SaveAs" Executed="SaveAsCommandBinding_Executed"/>
</Window.CommandBindings>

The commands are refering to the following code:

private void OpenCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
    Open();//Implementation of open file
}
private void SaveAsCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
    SaveAs();//Implementation of saveAs
}

2 - Associate the commands with the wanted keys:

<Window.InputBindings>
    <KeyBinding Key="O" Modifiers="Control" Command="Open"/>
    <KeyBinding Key="S" Modifiers="Control" Command="SaveAs"/>
</Window.InputBindings>

3 - Finally assign the commands with you menu item (InputGestureText is just a decorating text):

<Menu Name="menu1">
    <MenuItem Header="_File">
        <MenuItem Name="menuOpen" Header="_Open..." Command="Open" InputGestureText="Ctrl+O"/>
        <MenuItem Name="menuSaveAs" Header="_Save as..." Command="SaveAs" InputGestureText="Ctrl+S"/>
    </MenuItem>
</Menu>

That way multiple inputs may be associated to the same command.

florien
  • 487
  • 5
  • 16
Guish
  • 4,968
  • 1
  • 37
  • 39
  • 2
    Thank you.. this was very concise and helpful. Exactly the information I needed to convert my Menu Click handlers to Command handlers. – Adarsha Jul 12 '13 at 20:27
  • 3
    Some more details: Most of the [predefined commands](http://msdn.microsoft.com/en-us/library/ms752308.aspx#Command_Library) include input binding. E.g. the `Open` command. If you use such a command, you do not need to explicitly specify a `KeyBinding` or a `InputGestureText`. Moreover, you can omit the `Header`, if the default text is ok for you. – Martin Oct 16 '13 at 12:43
  • I have four different copy menu items - each calls a Copy command which is input bound to CTRL+C, but pass it different parameters. How can I make the shortcut key text on the context menu disappear? – Stephen Drew Mar 07 '14 at 09:01
  • OK - you can't set InputGestureText to null, because it defaults to the bound gesture - so I set it to a single space and it no longer shows. – Stephen Drew Mar 07 '14 at 10:17
  • ... it took for me quite some time to find out that if you want to get rid of CTRL, and associate it with a single one keystroke (like F1), you simply need to omit property Modifiers="Control" – Andrejs Gasilovs Sep 23 '16 at 09:20
69

You need to use KeyBindings (and CommandBindings if you (re)use RoutedCommands such as those found in the ApplicationCommands class) for that in the controls where the shortcuts should work.

e.g.

<Window.CommandBindings>
        <CommandBinding Command="New" Executed="CommandBinding_Executed" />
</Window.CommandBindings>
<Window.InputBindings>
        <KeyBinding Key="N" Modifiers="Control" Command="New"/>
</Window.InputBindings>

For custom RoutedCommands:

static class CustomCommands
{
    public static RoutedCommand DoStuff = new RoutedCommand();
}

usage:

<Window
    ...
    xmlns:local="clr-namespace:MyNamespace">
        <Window.CommandBindings>
                <CommandBinding Command="local:CustomCommands.DoStuff" Executed="DoStuff_Executed" />
        </Window.CommandBindings>
        <Window.InputBindings>
                <KeyBinding Key="D" Modifiers="Control" Command="local:CustomCommands.DoStuff"/>
        </Window.InputBindings>
    ...
</Window>

(It is often more convenient to implement the ICommand interface rather than using RoutedCommands. You can have a constructor which takes delegates for Execute and CanExecute to easily create commands which do different things, such implementations are often called DelegateCommand or RelayCommand. This way you do not need CommandBindings.)

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • 9
    I did this. But too much work for a simple thing. Why InputGestureText exists if dont work? – Felipe Pessoto Jan 13 '11 at 22:17
  • 5
    Noone said it would be simple; InputGestureText is just that: Text, it does nothing else. From the reference: "This property does not associate the input gesture with the menu item; it simply adds text to the menu item. The application must handle the user's input to carry out the action. For information on how to associate a command with a menu item, see Command." – H.B. Jan 13 '11 at 23:30
  • 5
    It used to be simple in WinForms; I guess that’s why people complain. – Roman Starkov Mar 17 '12 at 23:41
  • 10
    far too complicated, and too much boilerplate. – Jonathan. Apr 19 '13 at 17:15
  • @Jonathan.: Most of it can be implemented in an object-oriented abstraction that takes care of both the action and its shortcuts. – H.B. Apr 19 '13 at 17:39
  • 11
    But why? OOP isn't for everything. It's not a cure-all. Why make things more complicated than they need be? Windows Forms shortcuts are awesome: Click Menu Item > Go to Properties Pane > Choose corresponding shortcut > Save > Debug = WORKING! WPF: ICommand AbstractionLayer RoutedCommands KeyBindings CommandBindings DelegateCommand RelayCommand Execute CanExecute ICommand Interface OOP bloody this and that and everything else DEBUG oh something's not right retry. – Jase Feb 19 '15 at 17:10
  • @Jase: If you are used to it it's no problem at all, also you can probably make it automatically create the shortcuts in about ten lines of code. The way it's implemented in WPF is simply a lot more flexible and allows for input scoping. – H.B. Feb 19 '15 at 18:19
  • @Jonathan. I tend to agree, though I know it's at least partially because of my VB3-6 "roots". Does [my answer](http://stackoverflow.com/a/33787548/1028230) get closer to what you'd want to see, or is that too painfully kludgey? ;^) – ruffin Nov 20 '15 at 18:08
  • I might be late to the party, but how would you go about making a shortcut for a checkable menuitem? there is no command, all I want is a shortcut to the menuitem, where upon the key combination the menuitem IsChecked will get toggled. (the item has IsChecked bound to a boolean in the viewmodel class serving as a datacontext and I would rather not create a specific command to handle the toggling of the boolean) – CrookedBadge Jul 17 '17 at 09:11
  • @CrookedBadge: Sorry for the late response, you probably have come up with something usable by now. I would probably implement a generic command that works for all toggled values using reflection to access the affected property. I think i had this use-case before but i cannot remember where or how exactly i dealt with it. – H.B. Jul 21 '17 at 07:47
  • @H.B. Hi, thanks for your response. Yeah, with the customer requirement it turned out that doing it with InputBindings was the better solution, since they can be bound to components. My concern now is more of a philosophical nature. Checking and unchecking menu items seems to be something that should be handled in View, having gestures to be handled in ViewModel (which is the data context) feels like WPF is forcing the breaking of the MVVM pattern. Not the end of the world, just doesn't seem pretty :) – CrookedBadge Jul 23 '17 at 12:08
  • Just use an underscore in the Header property value. That works with the Alt key. – David Savage Dec 05 '19 at 08:34
  • @DavidSavage: That requires that the menu item is either at the top level or that the menu containing the item is opened. It is a completely separate mechanism and often you want a direct global keyboard shortcut. – H.B. Dec 05 '19 at 10:05
13

In my humble opinion is much easier just to use _ at the Header. This will create automatically the desired HotKey.

For example:

<MenuItem Header="_Editar">
<MenuItem Header="_Procurar" Name="MenuProcurar"
          InputGestureText="Ctrl+F"
          Click="MenuProcurar_Click">
    <MenuItem.ToolTip>
        <ToolTip>
            Procurar
        </ToolTip>
    </MenuItem.ToolTip>
</MenuItem>
</MenuItem>
Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
  • 14
    That creates a hotkey, not a shortcut. Doing something like Ctrl+F to launch a Find command without first navigating through menus is pretty common. – Mike Post Jul 03 '12 at 22:21
10

I'm overly biased by Windows.Forms & gulp VB 6, so I kind of agree with Jonathan and Jase that there's got to be a more straightforward/procedural method to statically wire up event handlers that aren't necessarily CommandBindings. And there is, I think.

A good tutorial for using non-CommandBinding handlers like this, but with an emphasis on buttons, can be found in this MSDN blog post, I believe. I'll distill and target MenuItems...

Creating the ICommand

First, create a class implementing ICommand. You can put this anywhere, of course, even in your MainWindow.xaml.cs file if you wanted, to keep your demo code insanely simple. You'll probably want to make CanExecute more complicated when you want to dis/en/able menu items later, but for now, we'll just always have our menu items enabled.

public class HelloWorldCommand : ICommand
{
    public void Execute(object parameter)
    {
        MessageBox.Show(@"""Hello, world!"" from " 
            + (parameter ?? "somewhere secret").ToString());
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;
} 

As the tutorial helpfully points out, you could call this command from anywhere already, with code like...

var hwc = new HelloWorldCommand();
if (hwc.CanExecute(this))
    hwc.Execute(this);

Declaring your command in the Window

So let's add a sort of "declaration" for the HelloWorldCommand to our Window so that we can use it later. Inside of your Window tags, register the command as a resource:

<Window.Resources>
    <local:HelloWorldCommand x:Key="hwc"/>
</Window.Resources>

Now we have a neat shortcut for linking to this "locally namespaced" command, "hwc", though you can obviously use any string you want. We'll use that a lot in our xaml.

Wiring up (and reusing!) the command

Let's add our MenuItems to our xaml. I've replaced the stock Grid with a DockPanel because that's the easiest way (for me) to have equi-spaced widgets that fill the Window, though I've left all of the rest of my UI out.

Note the Command="{StaticResource hwc}"s sprinkled into each MenuItem declaration. The key is the hwc in there - remember that that's our shortcut for the HelloWorldCommand that we set up at the Window level. And, of course, StaticResource says just to look it up the Window's resources. We're not binding anything; we're just using our shortcut.

<DockPanel LastChildFill="True">
    <Menu DockPanel.Dock="Top">
        <MenuItem Header="_File">
            <MenuItem 
                Header="_Open" 
                Command="{StaticResource hwc}" 
            >
                <MenuItem.CommandParameter>
                    <!-- so you could make this object as complex as you wanted, 
                        like, say, your entire Window. See magic incantation, below. -->
                    <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}" />
                </MenuItem.CommandParameter>

            </MenuItem>

            <MenuItem 
                Header="_Close" 
                Command="{StaticResource hwc}" 
                CommandParameter="Close"
                InputGestureText="Ctrl+G" />

            <MenuItem 
                Header="_Save" 
                Command="{StaticResource hwc}" 
                CommandParameter="Save" />

            <Separator />

            <MenuItem 
                Header="_Quit" 
                Command="{StaticResource hwc}" 
                CommandParameter="Quit" />
        </MenuItem>
</DockPanel>

CommandParameters to distinguish event sources

Note that we're using the same Command for everything! But how can we tell which widget threw the event? For that, you need to use the CommandParameter -- remember our Execute method's signature: Execute(object parameter). That CommandParameter parameter is what we can use to know how to handle the event. Try running this and note that the MessageBox will use whatever's in CommandParameter to let you know the source of the event. We're doing it all manually, but that's not too bad.

Also note that you can make these objects as complicated as you'd like. You can use a property in the MenuItem tag to define the parameter, or can use "real" <MenuItem.CommandParameter> tags, like in the Open menu item, above, to define something complex. In this case, we're passing the entire parent Window object, which was the easiest (though not the cleanest) way to throw our VB6-ish context into the event handler code.

Adding keyboard shortcuts to MenuItems (aka, "Answering the OP")

And now we can finally answer the original question! Let's finally wire up a keyboard shortcut for the Close menu item. You'll note that we've already declared an InputGestureText. By itself, InputGestureText is only cosmetic. If we were overly picky, we could argue the mechanism for creating the keyboard shortcut doesn't have any direct, inherent relationship with the MenuItem at all!

We need to instead (or additionally) register a listener for Ctrl-G at the Window level to catch the keystroke. So at the top level of your Window tags, insert this (taken essentially from here):

<Window.InputBindings>
    <KeyBinding Modifiers="Control"
                Key="G"
                Command="{StaticResource hwc}" 
                CommandParameter="window input binding"
    />
</Window.InputBindings>

Note that you could put CommandParameter tags in your KeyBinding by moving it from a self-closing piece of XML to "real" open and close KeyBinding tags.

And we're done. Run your app, and press Ctrl-G. Whaddup.

Pretty straightforward, once you've got the players straight, and much less magic binding-y than most intros to commands and MenuItems, I think.


Possible pro tip:

The whole CommandBinding thing confused me for a while. This is just for specific command types, I believe. That is, you can't just wire up any Command you like. Stuff like what's bragged about here (in what's admittedly a decent intro tutorial!)...

It might not be completely obvious, but by using commands, we just got a whole bunch of things for free: Keyboard shortcuts, text and InputGestureText on the items and WPF automatically enables/disables the items depending on the active control and its state. In this case, Cut and Copy are disabled because no text is selected, but Paste is enabled, because my clipboard is not empty!

... is kind of magic-y and not necessarily good, and can be confusing when you're new to WPF menus.

Community
  • 1
  • 1
ruffin
  • 16,507
  • 9
  • 88
  • 138
  • 1
    Downvotes without a quick comment makes it difficult to improve the answer. Say what's wrong, and I'll take a look! – ruffin Feb 26 '16 at 15:01
  • Don't know why you were downvoted, this is a high quality answer. – MKII Apr 21 '16 at 13:20
8

You can also declare RoutedUICommand in XAML:

<Window.Resources>
    <RoutedUICommand x:Key="BuildCmd" Text="Build">
        <RoutedUICommand.InputGestures>
            <KeyGesture>CTRL+SHIFT+B</KeyGesture>
        </RoutedUICommand.InputGestures>
    </RoutedUICommand>      
</Window.Resources>

Do the binding

<Window.CommandBindings>
    <CommandBinding Command="{StaticResource BuildCmd}" Executed="BuildCmdExecuted"/>
</Window.CommandBindings>

And in MenuItem

<MenuItem Command="{StaticResource BuildCmd}"/>

Another solution is discussed here.

leoly
  • 8,468
  • 6
  • 32
  • 33
  • 1
    Note that, as far as I know, this is the only way for a `MenuItem` the command is bound to, to _automatically_ display the gesture. Other techniques require explicitly setting the `InputGestureText` property of the `MenuItem`, but if the gesture is part of the command itself, it will show up in the menu item without any additional effort. Likewise setting the command's `Text` property. – Peter Duniho May 19 '15 at 02:20
  • IMHO, this is the simplest way to use RoutedUICommands to automatically populate MenuItems, Buttons etc with the right text, accelerator key information. This helped me a lot! – Bharat Mallapur Feb 23 '17 at 03:49
  • @Peter Duniho, `` is not the only method to automatically display the gesture. You just have to have the input bound to the command that the menu-item is calling for it to display. – Nathan Goings Jun 19 '18 at 19:31
  • 1
    @NathanGoings: _"You just have to have the input bound to the command"_ -- what command is that, if not a `RoutedUICommand`? I'm not aware of any other built-in command type that supports this. Note that I'm not saying you have to declare it in XAML, as in the posted answer, but you _do_ have to use a `RoutedUICommand` instance. If there's some other mechanism, please be more specific. – Peter Duniho Jun 20 '18 at 23:55
1

Here is solution in PowerShell:

  1. Define your XAML file:
<Window x:Class="WpfApp1.Window1"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="My App" Height="620" Width="950" >
    <Window.InputBindings>
        <KeyBinding Key="S" Modifiers="Ctrl"/>
    </Window.InputBindings>
    <Grid x:Name="MainGrid">
    <!--Your GUI is here-->
        <Menu Margin="0">
            <MenuItem Header="_File">
                <MenuItem x:Name="SaveProfile" Header="_Save Profile" InputGestureText="Ctrl+S"/>
            </MenuItem>
        </Menu>
    </Grid>
</Window>
  1. Add new type to be able to create Command(s)
Add-Type @"
public class DelegateCommand : System.Windows.Input.ICommand
{
    private System.Action<object> _action;
    public DelegateCommand(System.Action<object> action)
    {
        _action = action;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public event System.EventHandler CanExecuteChanged = delegate { };
    public void Execute(object parameter)
    {
        _action(parameter);
    }
}
"@
  1. Create and assign new Command to the previously defined KeyBinding, it is first key binding, that is why I'm addressing it with [0]. Note:in my case I saved the handle to the main Window in $hash.Window variable, you should put here the link to your main window object, that you created with [Windows.Markup.XamlReader]::Load($xamlXmlNodeReader) command or other window creation command.
$hash.Window.InputBindings[0].Command = New-Object DelegateCommand( { Save-Profile } )
  1. Create your function that you put into command
function Save-Profile {
    Write-Host "Save Profile"
    # Your logic goes here
}

Thanks to Nicholas Wolverson for the tips on how to create the Type for Command.

0

This work for me

<ContextMenu  PreviewKeyUp="ContextMenu_PreviewKeyUp">
    <MenuItem Header="Delete"  Click="DeleteID"   />
</ContextMenu>

Code behind:

private void ContextMenu_PreviewKeyUp(object sender, KeyEventArgs e)
{
    ContextMenu contextMenu = sender as ContextMenu;
    if (e.Key == Key.D)
    {
        //DELETE ID

    }
    contextMenu.IsOpen = false;
}
superchao
  • 7
  • 1