简体   繁体   English

WPF KeyBinding 吞咽键,防止 TextBox 使用

[英]WPF KeyBinding swallowing keys, preventing TextBox use

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 .任何在高于TextBox的级别定义的KeyBinding (没有分配修饰键)都会阻止用户在TextBox内键入这些键。

Minimal XAML Hierarchy:最小 XAML 层次结构:

<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. CommandKeyBinding是在第一个UserControl级别定义的。 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.所以在这个例子中,在文本框中,用户可以自由输入,直到按下A键,然后它只是不将字母插入文本框。 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.当您按下A键(和Handled = false )时,我可以清楚地看到TextBox.KeyDownTextBox.PreviewKeyDown正在触发,但字母不会添加到文本框的文本中,并且TextBox.PreviewTextInput不会触发。

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.我正在寻找任何可能表明正在吞下按键并阻止它被 TextBox 处理的任何建议,或者与我如何调试此问题相关的任何建议。

EDIT:编辑:
Thanks to Snoop , I was able to clearly see the problem.感谢Snoop ,我能够清楚地看到问题所在。

  1. TextBox.PreviewKeyDown tunnels down and fires through the visual tree, starting at the Window, and ending at the TextBox TextBox.PreviewKeyDown向下隧道并通过可视化树触发,从 Window 开始,在 TextBox 结束
  2. TextBox.KeyDown bubbles back up starting at the TextBox and heading towards the window TextBox.KeyDown气泡从 TextBox 开始向上并朝向窗口
  3. TextBox.KeyDown gets Handled set to true by the first UserControl that has the KeyBinding set. TextBox.KeyDown由第一个设置了 KeyBinding 的 UserControl 将 Handled 设置为 true。
  4. TextBox.PreviewTextInput never fires, nor does the textbox process the input, because the KeyDown event was set as handled. TextBox.PreviewTextInput永远不会触发,文本框也不会处理输入,因为 KeyDown 事件被设置为已处理。

This still leaves the problem, how do you prevent the UserControl from handling the input if a textbox has focus?这仍然存在问题,如果文本框有焦点,如何防止 UserControl 处理输入? Within the Command execution, I can check if a textbox has keyboard focus, but by this time it's too late.在命令执行中,我可以检查文本框是否具有键盘焦点,但此时为时已晚。

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我查看了 key bindind 的文档,并描述了您绑定的键不应该只是键,而是键手势,所以它应该是

  • Modifier key + normal key修饰键+普通键
  • Numeric keypad key数字键盘键
  • Functional key.功能键。

Of course, it works with just A, but it's bad practice overall.当然,它只适用于 A,但总体而言这是不好的做法。 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更多信息请访问https://msdn.microsoft.com/cs-cz/library/system.windows.input.keybinding(v=vs.110).aspx

TextInput and PreviewTextInput only fires when the Text actually changes / might change. TextInputPreviewTextInput仅在 Text 实际更改/可能更改时触发。

As you updated your question to reflect, the Command intercepts the event and the (Preview)TextInput events are never raised.当您更新您的问题以反映时, Command拦截事件,并且永远不会引发 (Preview)TextInput 事件。

The nicest solution would be to add a modifier key to your KeyBinding, but I suspect that is not your preferred way to go.最好的解决方案是在您的 KeyBinding 中添加一个修饰键,但我怀疑这不是您的首选方式。

Another option would be to e.Handle the PreviewKeyDown event on the TextBox and raise the TextComposition events yourself, using something like:另一种选择是e.Handle TextBox 上的 PreviewKeyDown 事件并自己引发 TextComposition 事件,使用以下内容:

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 ) (可替换地,插入textBox.Text在正确CaretIndex

Truth be told, it would still be a hack.说实话,这仍然是一个黑客。

I had used the TextComposition RaiseEvent approach for years, however this seems to break typing for non-latin keyboard layouts (eg. cyrillic).多年来,我一直使用TextComposition RaiseEvent方法,但这似乎破坏了非拉丁键盘布局(例如西里尔文)的打字。

The proper way to do this is to derive from InputBinding and return false in the Matches?正确的方法是从 InputBinding 派生并在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:现在您有一个 InputGesture 它将忽略来自文本框的事件,您可以像往常一样在 XAML 中使用它:

<UserControl.InputBindings>
    <nsp:BareKeyBinding
        Key="D"
        Command="{StaticResource Commands.YourCommand}"
        CommandParameter="None" />
</UserControl.InputBindings>

As long as you use KeyBinding this not going to work without major hacks.只要您使用KeyBinding这在没有重大黑客攻击的情况下就无法工作。 A solution I implemented for this is:我为此实施的解决方案是:

  1. Use the KeyDown event to capture those keys being pressed (instead of KeyBindings ).使用KeyDown事件来捕获被按下的那些键(而不是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).这将在您的代码隐藏中,从那里您需要打开按下的键以调用所需的命令(在您的情况下为SomeCommand )。
  2. Now you have a different problem.现在你有一个不同的问题。 The TextBox is getting the input but your key-bound commands are also firing. TextBox正在获取输入,但您的键绑定命令也在触发。 On the code behind, check the type of keyEventArgs.InputSource and ignore the key stroke if it's a TextBox .在后面的代码中,检查keyEventArgs.InputSource的类型,如果是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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM