Reputation: 17213
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.
TextBox.PreviewKeyDown
tunnels down and fires through the visual tree, starting at the Window, and ending at the TextBoxTextBox.KeyDown
bubbles back up starting at the TextBox and heading towards the windowTextBox.KeyDown
gets Handled set to true by the first UserControl that has the KeyBinding set.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.
Upvotes: 9
Views: 5789
Reputation: 17213
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="{...}" />
</UserControl.InputBindings>
Upvotes: 2
Reputation: 3902
As long as you use KeyBinding
this not going to work without major hacks. A solution I implemented for this is:
KeyDown
event to capture those keys being pressed (instead of KeyBindings
). This will be on your code-behind and from there you'll need to switch on the pressed Key to call the required command (SomeCommand
in your case).TextBox
is getting the input but your key-bound commands are also firing. On the code behind, check the type of keyEventArgs.InputSource
and ignore the key stroke if it's a TextBox
.It should look like this:
private void OnKeyDown(object sender, KeyEventArgs e)
{
ICommand command = null;
switch (e.Key)
{
case Key.A:
command = Commands.SomeCommand;
break;
case Key.B:
command = Commands.SomeOtherCommand;
break;
}
bool isSourceATextBox = e.InputSource.GetType() == typeof(TextBox);
if (command != null && !isSourceATextBox)
{
command.Execute(parameter:null);
}
}
Upvotes: 1
Reputation: 64
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
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
Upvotes: 1
Reputation: 4631
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.
Upvotes: 1