Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF KeyBinding swallowing keys, preventing TextBox use

Tags:

c#

.net

wpf

xaml

Problem Overview:
Any KeyBinding's defined at a level higher than a TextBox (with no modifier keys assigned), prevents the user from typing those keys inside the TextBox.

Minimal XAML Hierarchy:

<Window>
  <UserControl>
    <Border>
      <UserControl>
        <TextBox>

Minimal Command/KeyBinding:

<UserControl.Resources>
    <RoutedUICommand x:Key="Commands.SomeCommand" />
</UserControl.Resources>
<UserControl.InputBindings>
    <KeyBinding Key="A" Command="{StaticResource Commands.SomeCommand}" />
</UserControl.InputBindings>
<UserControl.CommandBindings>
    <CommandBinding Command="{StaticResource Commands.SomeCommand}" Executed="..." />
</UserControl.CommandBindings>

The Command and KeyBinding, are defined at the first UserControl level. So in this example, in the textbox, the user can type freely until they press the A key, and then it just does not insert the letter into the textbox. I can clearly see that the TextBox.KeyDown and TextBox.PreviewKeyDown are firing when you press the A key (and Handled = false) , but the letter will not get added to the text of the textbox and TextBox.PreviewTextInput does not fire.

I'm looking for any suggestions that may indicate what is swallowing the keypress and stopping it from getting processed by the TextBox, or anything related to how I can debug this issue.

EDIT:
Thanks to Snoop, I was able to clearly see the problem.

  1. TextBox.PreviewKeyDown tunnels down and fires through the visual tree, starting at the Window, and ending at the TextBox
  2. TextBox.KeyDown bubbles back up starting at the TextBox and heading towards the window
  3. TextBox.KeyDown gets Handled set to true by the first UserControl that has the KeyBinding set.
  4. TextBox.PreviewTextInput never fires, nor does the textbox process the input, because the KeyDown event was set as handled.

This still leaves the problem, how do you prevent the UserControl from handling the input if a textbox has focus? Within the Command execution, I can check if a textbox has keyboard focus, but by this time it's too late.

like image 312
caesay Avatar asked Oct 14 '15 19:10

caesay


3 Answers

I have the same problem. I took a look to documentation for key bindind, and there is described, that the key on which you bind shouldn't be just key, but key gesture, so it shall be

  • Modifier key + normal key
  • Numeric keypad key
  • Functional key.

Of course, it works with just A, but it's bad practice overall. You should consider to implement some of the posibilities mentioned behind. More at https://msdn.microsoft.com/cs-cz/library/system.windows.input.keybinding(v=vs.110).aspx

like image 84
Marek Mikulec Avatar answered Nov 12 '22 08:11

Marek Mikulec


TextInput and PreviewTextInput only fires when the Text actually changes / might change.

As you updated your question to reflect, the Command intercepts the event and the (Preview)TextInput events are never raised.

The nicest solution would be to add a modifier key to your KeyBinding, but I suspect that is not your preferred way to go.

Another option would be to e.Handle the PreviewKeyDown event on the TextBox and raise the TextComposition events yourself, using something like:

target.RaiseEvent(new TextCompositionEventArgs(InputManager.Current.PrimaryKeyboardDevice, 
new TextComposition(InputManager.Current, target, "A")) 
{ 
    RoutedEvent = TextCompositionManager.TextInputEvent 
});

(Alternatively, insert into textBox.Text at the correct CaretIndex)

Truth be told, it would still be a hack.

like image 1
Troels Larsen Avatar answered Nov 12 '22 07:11

Troels Larsen


I had used the TextComposition RaiseEvent approach for years, however this seems to break typing for non-latin keyboard layouts (eg. cyrillic).

The proper way to do this is to derive from InputBinding and return false in the Matches? check if the event originated from a text-box.

/// <summary>
/// This gesture doesn't handle keys originating in a text control. This allows key bindings without modifier keys
/// that don't break normal typing. A standard KeyGesture doesn't have such logic; this allows the parent of a
/// text box to handle such bare keypresses before the textbox gets to see it as normal text input, thus breaking
/// normal typing.
/// </summary>
public class BareKeyGesture : InputGesture
{
    public Key Key { get; set; }

    public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
    {
        var keyEventArgs = inputEventArgs as KeyEventArgs;
        if (keyEventArgs == null)
            return false;

        if (inputEventArgs.OriginalSource is TextBoxBase)
            return false;

        return (int)Key == (int)keyEventArgs.Key && Keyboard.Modifiers == ModifierKeys.None;
    }
}

/// <summary>
/// This only exists because the InputBinding constructor is protected, but since we have to have it anyway
/// we also use this opportunity to simplify adding a BareKeyGesture to it.
/// </summary>
public class BareKeyBinding : InputBinding
{
    private BareKeyGesture _gesture = new();

    public BareKeyBinding()
    {
        Gesture = _gesture;
    }

    public Key Key
    {
        get => _gesture.Key;
        set { _gesture.Key = value; }
    }
}

And now that you have an InputGesture which will ignore events originating from textboxes, you can use it in XAML like normal:

<UserControl.InputBindings>
    <nsp:BareKeyBinding
        Key="D"
        Command="{StaticResource Commands.YourCommand}"
        CommandParameter="None" />
</UserControl.InputBindings>
like image 1
caesay Avatar answered Nov 12 '22 08:11

caesay