Can I create a KeyBinding for a sequence of keys in WPF?
Asked Answered
H

3

3

Is it possible to define key bindings in WPF for a sequence of key presses like the shortcuts in Visual Studio e.g. Ctrl + R, Ctrl + A is run all tests in current solution

As far as I can see I can only bind single key combinations like Ctrl + S using the element. Can I bind sequences using this or will I have to manually handle the key presses to do this?

Henricks answered 2/9, 2010 at 9:40 Comment(0)
T
5

You need to create your own InputGesture, by overriding the Matches method.

Something like that:

public class MultiInputGesture : InputGesture
{
    public MultiInputGesture()
    {
        Gestures = new InputGestureCollection();
    }

    public InputGestureCollection Gestures { get; private set; }

    private int _currentMatchIndex = 0;

    public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
    {
        if (_currentMatchIndex < Gestures.Count)
        {
            if (Gestures[_currentMatchIndex].Matches(targetElement, inputEventArgs))
            {
                _currentMatchIndex++;
                return (_currentMatchIndex == Gestures.Count);
            }
        }
        _currentMatchIndex = 0;
        return false;
    }
}

It probably needs a little more than that, like ignoring certain events (e.g. KeyUp events between KeyDown events shouldn't reset _currentMatchIndex), but you get the picture...

Ting answered 2/9, 2010 at 10:2 Comment(1)
Have done something very similar to what you suggest in the end, requires a bit more logic since an InputGesture gets every key press (i.e. includes pressing Ctrl, Shift, Alt etc) and you need to take care of the fact that if a user doesn't press the combination in quick succession they probably didn't mean to press itHenricks
S
1

The answer by @ThomasLevesque is mostly correct, but doesn't deal with repeating keys. (Note that holding the Ctrl key down causes key-repeat events to be generated.) It can also be useful to time out if the user stalls mid-sequence. Here's what I'm using:

public class MultiKeyInputGesture : InputGesture {
    private const int MAX_PAUSE_MILLIS = 1500;

    private InputGestureCollection mGestures = new InputGestureCollection();

    private DateTime mLastWhen = DateTime.Now;
    private int mCheckIdx;

    public MultiKeyInputGesture(KeyGesture[] keys) {
        Debug.Assert(keys.Length > 0);

        // Grab a copy of the array contents.
        foreach (KeyGesture kg in keys) {
            mGestures.Add(kg);
        }
    }

    public override bool Matches(object targetElement, InputEventArgs inputEventArgs) {
        if (!(inputEventArgs is KeyEventArgs)) {
            // does this actually happen?
            return false;
        }

        DateTime now = DateTime.Now;
        if ((now - mLastWhen).TotalMilliseconds > MAX_PAUSE_MILLIS) {
            mCheckIdx = 0;
        }
        mLastWhen = now;

        if (((KeyEventArgs)inputEventArgs).IsRepeat) {
            // ignore key-repeat noise (especially from modifiers)
            return false;
        }

        if (!mGestures[mCheckIdx].Matches(null, inputEventArgs)) {
            mCheckIdx = 0;
            return false;
        }

        mCheckIdx++;
        if (mCheckIdx == mGestures.Count) {
            mCheckIdx = 0;
            inputEventArgs.Handled = true;
            return true;
        }

        return false;
    }
}

I'm using this by defining the RoutedUICommand in XAML:

<Window.Resources>
    <RoutedUICommand x:Key="MyCommand" Text="My Command"/>
</Window.Resources>

This is referenced from <Window.CommandBindings> and <MenuItem> as usual. Then, in the window constructor, I do:

RoutedUICommand ruic = (RoutedUICommand)FindResource("MyCommand");
ruic.InputGestures.Add(
    new MultiKeyInputGesture(new KeyGesture[] {
          new KeyGesture(Key.H, ModifierKeys.Control, "Ctrl+H"),
          new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl+C")
    }) );

I found this forum post helpful.

You will need to add an explicit InputGestureText to any MenuItem, unless you want to try the DisplayString hack in the linked forum post.

NOTE: the key gesture handler "eats" the key that completes the gesture. If you have more than one handler, and the user tries to use two multi-key sequences in a row (e.g. Ctrl+H, Ctrl+C followed immediately by Ctrl+H, Ctrl+D), the second handler won't reset when Ctrl+C is hit. Instead, it'll reset when the second Ctrl+H arrives, and will miss the combo. The actual behavior is dependent upon the order in which the handlers are called. I'm currently handling this by defining a static event that fires when a match is found, and subscribing all instances to it.

Update: one other thing to be aware of: the order of items in <Window.CommandBindings> matters. If you have a Copy handler that fires on Ctrl+C, it must appear in the list after the multi-key gesture for Ctrl+H, Ctrl+C.

Scampi answered 4/6, 2019 at 22:31 Comment(0)
B
-1
   <KeyBinding x:Name="mykeybinding" Gesture="CTRL+P" Key="E" 
                 Command="mycommand"/>

That seems to do trick at my end, I have have to press ctrl+P+E to execute "mycommand"

Based on http://msdn.microsoft.com/en-in/library/system.windows.input.keybinding.aspx

Bausch answered 16/4, 2013 at 8:47 Comment(1)
From MSDN "In the case when the Key, Modifiers, and the Gesture attributes are all set, the attribute which is defined last will be used for the KeyGesture. You can potentially have situations where for example a Key set last will overwrite just the Key component of a preceding Gesture but leave the Gesture's modifiers the same." - so you are ending up with Ctrl+E as your gesture not the sequence which was what I was afterHenricks

© 2022 - 2024 — McMap. All rights reserved.