6

I'm writing my first WPF application and am trying to get my custom commands to work.

public static RoutedUICommand Header1 { get; private set; }

.
.
.

gestures = new InputGestureCollection();
gestures.Add(new KeyGesture(Key.D1, ModifierKeys.Control, "Ctrl+1"));
Header1 = new RoutedUICommand("Header 1", "Header1", typeof(EditCommands), gestures);

I then added a CommandBindings section to my window's XAML.

<!-- local refers to my application's namespace -->
<Window.CommandBindings>
    <CommandBinding Command="local:EditCommands.Header1" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute"></CommandBinding>
</Window.CommandBindings>

And, finally, added a command entry to the associated Ribbon control.

<RibbonButton Label="Header 1" Command="local:EditCommands.Header1" SmallImageSource="Images\small.png" ToolTipTitle="Header 1" ToolTipDescription="" ToolTipImageSource="Images\small.png"></RibbonButton>

Clicking the Ribbon button executes the handler as expected. However, pressing Ctrl+1 seems to have no effect at all. How can I have my hot key recognized?

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • @AnjumSKhan: NumLock shouldn't affect `Key.D1` key behavior (there is `Key.NumPad1` key for this case). – Dennis Dec 28 '15 at 06:46
  • @AnjumSKhan: None of my hot keys appear to be recognized. – Jonathan Wood Dec 28 '15 at 06:47
  • @JonathanWood Hi, I checked with various Hot-Key combos including urs, its working fine here. Plz check if your `Command` is actually getting executed at first place. Also plz try to use a normal `Button` for your `Command` and verify. – AnjumSKhan Dec 28 '15 at 06:56
  • @AnjumSKhan: If I click the RibbonButton, the command is executed. But none of my commands are executed using the hot key. I tried adding a regular button--it's just disabled right now. I can play with it some more. How did you define the command and bind it to your window? – Jonathan Wood Dec 28 '15 at 07:05
  • Try defining a keybinding on xaml instead of code behind: ``, in your Window.InputBindings section. Maybe you will enclose the failure. – Jose Dec 28 '15 at 13:08
  • Working fine also for me... Can you show a little more of your `EditCommands` class? Where is the second code block (`gestures = new InputGestureCollection()` ...) called? Where does the `gestures` variable come from (global / local?), and where else do you use it? – andreask Jan 02 '16 at 08:45
  • 1
    Also, how does your window's `CommandBinding_CanExecute` method look like? – andreask Jan 02 '16 at 08:53
  • @andreask: The code that creates the `gestures` is in a static constructor for `EditCommands`. My `CommandBinding_CanExecute` is a little involved as it performs a lookup on a collection to determine if the command is enabled. But suffice to say that the Ribbon button becomes enabled so it must be setting `CanExecute` to `true`. – Jonathan Wood Jan 02 '16 at 18:40
  • Something grabbing keyboard focus perhaps? If you tab to the ribbon button (to ensure it has focus) does the hotkey work then? Also, change the CommandBinding Command to "{x:Static local:EditCommands.Header1}". I've come across some styling funnies in the past, where TargetType="Button" doesn't work but TargetType="{x:Type Button}" does, and wondered if x:Static may be the same. Always best to be explicit. – Andrew Stephens Jan 08 '16 at 11:45
  • Man, so many people really want this bounty. And also @Dennis, are you sure NumLock doesn't affect this? – OneStig Jan 09 '16 at 05:22

5 Answers5

2

Something else must be going on. Could there be some element that handles the key input before it reaches the element with the command bindings that contain your command?

A useful tool for figuring out things like that is Snooop.

Using the code below HandleHeader1 is called whenever Ctrl+1 is pressed.

public static class MyCommands
{
    public static RoutedUICommand Header1 { get; } = new RoutedUICommand("Header 1", "Header1", typeof(MyCommands), new InputGestureCollection { new KeyGesture(Key.D1, ModifierKeys.Control, "Ctrl+1") });
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void HandleHeader1(object sender, ExecutedRoutedEventArgs e)
    {
    }
}

<Window x:Class="WpfApplication2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApplication2"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="local:MyCommands.Header1" Executed="HandleHeader1"/>
    </Window.CommandBindings>
    <StackPanel>
        <TextBox/>
    </StackPanel>
</Window>
Johan Appelgren
  • 331
  • 1
  • 5
  • Well if that works I can't see why mine doesn't. I did notice that the third argument to the `RoutedUICommand` constructor was `typeof(MainWindow)` instead of the type of the class that holds the commands (which would be `MyCommands` in your code). I don't know what that should be but neither seems to work for me. I haven't added anything else to handle any commands. – Jonathan Wood Jan 04 '16 at 20:14
  • The only difference I see in my code is that I also reference the command from some other elements (a `RibbonButton` in my case). – Jonathan Wood Jan 04 '16 at 20:26
  • The Type argument was just a mistake on my part. But as you've noticed it shouldn't make any difference in this case. – Johan Appelgren Jan 05 '16 at 07:55
  • I don't think referencing the command from multiple elements should make any difference. I don't have access to Ribbon right now, but it works fine if I add a regular menu to my example. – Johan Appelgren Jan 05 '16 at 07:56
  • Did you try Snoop to see where the key input is handled instead of your root Window element? https://github.com/cplotts/snoopwpf – Johan Appelgren Jan 05 '16 at 08:01
2

Hi I completely agree with Johan and Jane, your code is worked for me. But I can reproduce that strange behavior only in one case. If you have several command with the same gesture only the first one that defined in Window.CommandBindings section will be triggered. Here is my code and you can check it out. 1. Xaml:

<ribbon:RibbonWindow x:Class="SoRibbonWpfApplivationHelpAttempt.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
    xmlns:soRibbonWpfApplivationHelpAttempt="clr-namespace:SoRibbonWpfApplivationHelpAttempt"
    Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
    <CommandBinding Command="soRibbonWpfApplivationHelpAttempt:EditCommands.PandaButton" Executed="CommandBinding_Executed_P" CanExecute="CommandBinding_CanExecute_P"></CommandBinding>
    <CommandBinding Command="soRibbonWpfApplivationHelpAttempt:EditCommands.Header1" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute"></CommandBinding>

</Window.CommandBindings>
<Grid>
    <ribbon:Ribbon x:Name="RibbonWin"  SelectedIndex="0" IsEnabled="True">
        <ribbon:Ribbon.HelpPaneContent>
            <ribbon:RibbonButton SmallImageSource="Images/Penguins.jpg" 
                                 ToolTipTitle="Header 1" ToolTipDescription="" ToolTipImageSource="Images/Penguins.jpg"
                                 Command="soRibbonWpfApplivationHelpAttempt:EditCommands.Header1"/>
        </ribbon:Ribbon.HelpPaneContent>
        <ribbon:Ribbon.QuickAccessToolBar>
            <ribbon:RibbonQuickAccessToolBar>
                <ribbon:RibbonButton x:Name ="Save" SmallImageSource="Images\Koala.jpg" 
                                     ToolTipTitle="Header 1" ToolTipDescription="" ToolTipImageSource="Images/Koala.jpg"
                                     Command="soRibbonWpfApplivationHelpAttempt:EditCommands.PandaButton"/>
            </ribbon:RibbonQuickAccessToolBar>
        </ribbon:Ribbon.QuickAccessToolBar>
    </ribbon:Ribbon>
    <TextBox VerticalAlignment="Bottom" HorizontalAlignment="Stretch" BorderBrush="Red"/>
</Grid>

2. Code behind:

    public partial class MainWindow : RibbonWindow
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        Debug.WriteLine("header 1");
    }

    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    private void CommandBinding_CanExecute_P(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    private void CommandBinding_Executed_P(object sender, ExecutedRoutedEventArgs e)
    {
        Debug.WriteLine("panda");
    }
}

public class EditCommands
{
    public static RoutedUICommand Header1 { get; private set; }

    public static RoutedUICommand PandaButton
    {
        get;
        private set;
    }

    static EditCommands()
    {
        var gestures = new InputGestureCollection {new KeyGesture(Key.D1, ModifierKeys.Control, "Ctrl+1")};
        Header1 = new RoutedUICommand("Header 1", "Header1", typeof(EditCommands), gestures);

        var pandaG = new InputGestureCollection { new KeyGesture(Key.D2, ModifierKeys.Control, "Ctrl+2") };
        PandaButton = new RoutedUICommand("Panda Button", "PandaButton", typeof(EditCommands), gestures);
    }
}

So I can suggest you the only one thing; to check if there are some another commands (may be not custom) which are already using the gesture you try to define.

Regards,

Ilan
  • 2,762
  • 1
  • 13
  • 24
1

Found this example. Perhaps you are missing the KeyBinding and InputBindings.Add parts.

KeyGesture keyg = new KeyGesture(Key.V, ModifierKeys.Control);  
KeyBinding kb = new KeyBinding(Window1.CtrlVCommand,keyg);  
InputBindings.Add(kb); 

Your code looks very similar but missing these from what you have shown.

I found this about 5 links down after searching wpf ribbon button shortcut key.

To make the aforementioned sample work with Ctrl+1 like you want, I updated this line, as so:

KeyGesture keyg = new KeyGesture(Key.D1, ModifierKeys.Control);

If this does not work, perhaps you can take the above working sample, start adding the pieces from your project into it and see when/where it breaks. If you can post a complete, minimal solution that exhibits this issue, perhaps I/we can debug.

More information:

In researching this more, I came across this reference on input bindings and #1084 A KeyBinding Binds a Command to a Key

A user interface element has a CommandBindings collection containing command binding objects that indicate which commands are supported for the element and the code that the command is bound to.

User interface elements also have an InputBindings collection that contains KeyBinding and MouseBinding instances, each of which maps keyboard or mouse input to a command that is also present in the CommandBindings collection.

Lastly, there is the MSDN on InputBinding

Community
  • 1
  • 1
Kory Gill
  • 6,993
  • 1
  • 25
  • 33
  • I really don't get what's the point of specifying the keys in the `KeyGesture` if I have to specify them somewhere else as well. Also, your code to create a `KeyGesture` looks the same as mine except you don't specify the keystroke description. How would that help? – Jonathan Wood Jan 03 '16 at 05:37
  • Did you try the sample code I found when trying to research an answer to your question and add to it to mimic your project? I copied the code to my machine, changed the 1 line as shown above, and it works. I looked at some other code I have, and they all work in this way, so I never questioned it. If what I wrote does not help, perhaps someone else will provide something. – Kory Gill Jan 03 '16 at 05:51
  • I have been looking at it. I don't understand what `InputBindings` is. I see now what you're saying about modifying that line. I will continue to study that code. – Jonathan Wood Jan 03 '16 at 05:59
1

The code below is working for me, if i click the "Header 1" ribbon item or use Ctrl+1, "Executed" is printed to output in console. So I have to agree with Johan that something else must be going on in your application mainwindow than what you are showing us.

Commands

public class EditCommands
{
    public static RoutedUICommand Header1 { get; private set; }

    static EditCommands()
    {
        var gestures = new InputGestureCollection();
        gestures.Add(new KeyGesture(Key.D1, ModifierKeys.Control, "Ctrl+1"));
        Header1 = new RoutedUICommand("Header 1", "Header1", typeof(EditCommands), gestures);
    }
}

XAML

<Window.CommandBindings>
    <CommandBinding Command="local:EditCommands.Header1" Executed="CommandBinding_OnExecuted" CanExecute="CommandBinding_OnCanExecute"></CommandBinding>
</Window.CommandBindings>

<Grid>
    <Ribbon>
        <RibbonButton Label="Header 1" Command="local:EditCommands.Header1" ToolTipTitle="Header 1" ToolTipDescription=""/>
    </Ribbon>
</Grid>

Code behind

private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
    Console.WriteLine("Executed");
}

private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}
Janne Matikainen
  • 5,061
  • 15
  • 21
0

What you are trying to implement here can be addressed with direct InputBindings to the Windows or the container control you want!

<Window.InputBindings>
        <KeyBinding Command="{x:Static local:EditCommands.Header1}" Gesture="CTRL+1" />
 </Window.InputBindings>

I would go with this method in order to separate the presentation logic from the business logic (and since you are already binding to Commands it might be a good idea to follow the MVVM pattern). In addition to this you can also bind the same shortcuts to different container controls!

Force focus to user control:

<Window FocusManager.FocusedElement="{Binding ElementName=ribbon}">
   <RibbonButton x:Name="ribbon" Label="Header 1" Command="local:EditCommands.Header1" Focusable="True"/>
</Window>
  • Apparently, there is more needed to make this work as this doesn't activate the commands. Even more troublesome is that I don't understand why I need to specify the hotkey in so many different places. Back in the old days, we had to design our VB6 applications using intuitive drag and drop, and double clicks. So far, I'm not liking WPF at all. – Jonathan Wood Jan 04 '16 at 19:58
  • There is no need to specify anything else apart from the InputBindins. Your problem might be that the key-combination is captured by another container control. Try to focus on the ribbon first and then use the shortcut! You can work-around this by either forcing the initial focus to the ribbon or disallow focus on the container that captures the shortcut. – Michael Kanios Jan 05 '16 at 07:30
  • If your shortcuts are captured by a textbox, then look at this article on how to disable default textbox shortcuts: http://stackoverflow.com/questions/12941707/keybinding-in-usercontrol-doesnt-work-when-textbox-has-the-focus – Michael Kanios Jan 05 '16 at 07:39