Keyboard.GetKeyStates误报,我如何才能真正知道是否按下了某个键? [英] Keyboard.GetKeyStates false positive, how do I actually know if a key is pressed?

查看:167
本文介绍了Keyboard.GetKeyStates误报,我如何才能真正知道是否按下了某个键?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Keyboard.GetKeyStates似乎有一种返回错误按下的键的方法,例如,即使未按下Keyboard.GetKeyStates(Key.NumPad2)也可以返回Down, Toggled.

There seem to be a way for Keyboard.GetKeyStates to return wrongly pressed keys, i.e for instance for Keyboard.GetKeyStates(Key.NumPad2) to return Down, Toggled even if not pressed.

我已经能够在一个非常简单的WPF应用程序中捕获keyUp事件来重现此内容. 重现该错误的方法如下:

I have been able to reproduce this on a very simple WPF app catching keyUp events. The way to reproduce the bug is the following:

  1. 按NumPad2
  2. 按LShift
  3. 释放NumPad2
  4. 释放LShift

此后,检查NumPad2的状态将始终产生Down, Toggled,直到再次按下并释放它为止.

From then on, checking the state for NumPad2 will always yield Down, Toggled until you press and release it again.

不确定是否重要,但是我在 Windows 8.1 x64

Not sure it matters, but I am using the English UK Extended keyboard on Windows 8.1 x64

原因似乎是LShift-NumPad2实际上等效于Down键(有意义),但是Windows似乎没有发现这意味着不再按下NumPad2.

The reason seems to be that LShift-NumPad2 is actually equivalent to the Down key (makes sense), but Windows doesn't seem to catch that this means NumPad2 is not pressed anymore.

我的测试应用程序只是捕获了KeyDown和KeyUp,并向我显示了每个事件的KeyStates更改以及每个事件之后整个键盘的KeyStates列表(我将其与应用程序启动时的状态进行了比较,以免用NumLock键和其他键的状态污染输出.

My test app simply catches KeyDown and KeyUp, and shows me the KeyStates changes for each event as well as a list of KeyStates for the whole keyboard after each event (I compare it to the state when the application started in order to not pollute the output with the state of the NumLock keys and others).

这是我在之前的测试中得到的输出:

This is the output I get with the previous test:

MainWindow_OnKeyDown NumPad2: Down, Toggled -> Down, Toggled
KeyStates:
    NumPad2: Down, Toggled

MainWindow_OnKeyDown LeftShift: None -> Down, Toggled
KeyStates:
    NumPad2: Down, Toggled
    LeftShift: Down, Toggled

MainWindow_OnKeyUp LeftShift: Down, Toggled -> Toggled
KeyStates:
    NumPad2: Down, Toggled
    LeftShift: Toggled

MainWindow_OnKeyUp Down: None -> None
KeyStates:
    NumPad2: Down, Toggled
    LeftShift: Toggled

MainWindow_OnKeyUp LeftShift: Toggled -> None
KeyStates:
    NumPad2: Down, Toggled

尽管您在第3步和第3步松开了NumPad2键,但在第3步和第4步后仍按NumPad2键.

So as you can see the NumPad2 key shows as pressed after steps 3 and 4 although I released it at step 3.

这是xaml的完整代码,以及后面的代码,以防您要将其直接复制/粘贴到新项目中并希望在实际操作中看到它:

Here is the complete code for the xaml and code behind in case you want to copy/paste this straight into a new project and want to see it in action:

<Window x:Class="WpfKeyboardTester.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        Loaded="MainWindow_OnLoaded"
        KeyDown="MainWindow_OnKeyDown"
        KeyUp="MainWindow_OnKeyUp"
        WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ScrollViewer
            Grid.Row="0"
            Name="ScrollViewer"
            x:FieldModifier="private">
            <TextBox
                Name="TextBox"
                IsReadOnly="True"
                x:FieldModifier="private"/>
        </ScrollViewer>
        <Button
            Grid.Row="1"
            Click="Clear_OnClick">
            Clear
        </Button>
    </Grid>
</Window>

还有

   public partial class MainWindow
   {
      private Dictionary<Key, KeyStates> _initialKeyStates;
      private Dictionary<Key, KeyStates> _keyStates;
      private Key _previousKeyDown;
      private Key _previousKeyUp;

      public MainWindow()
      {
         InitializeComponent();
      }

      private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
      {
         _keyStates = GetKeyStates();
         _initialKeyStates = _keyStates;
      }

      private void MainWindow_OnKeyDown(object sender, KeyEventArgs e)
      {
         if (e.Key != _previousKeyDown)
         {
            AppendKeyEventDescription("MainWindow_OnKeyDown", e);
            _previousKeyDown = e.Key;            
         }
      }

      private void MainWindow_OnKeyUp(object sender, KeyEventArgs e)
      {
         if (e.Key != _previousKeyUp)
         {
            AppendKeyEventDescription("MainWindow_OnKeyUp", e);
            _previousKeyUp = e.Key;
         }
      }

      private void Clear_OnClick(object sender, RoutedEventArgs e)
      {
         TextBox.Text = string.Empty;
      }

      private static Dictionary<Key, KeyStates> GetKeyStates()
      {
         return Enum.GetValues(typeof (Key))
            .Cast<Key>()
            .Distinct()
            .Where(x => x != Key.None)
            .ToDictionary(
               x => x,
               Keyboard.GetKeyStates);
      }

      private void AppendKeyEventDescription(string eventName, KeyEventArgs e)
      {
         if (TextBox.Text != string.Empty)
            TextBox.Text += "\n\n";
         TextBox.Text +=
            eventName + " " + e.Key + ": " + _keyStates[e.Key] + " -> " + e.KeyStates;

         _keyStates = GetKeyStates();

         var changedKeys = _keyStates.Keys
            .Where(key => _keyStates[key] != _initialKeyStates[key])
            .ToArray();

         if (changedKeys.Any())
         {
            TextBox.Text += changedKeys.Aggregate(
               "\nKeyStates:",
               (text, key) => text + "\n\t" + key + ": " + _keyStates[key]);
         }
      }
   }

我使用Win32 API调用探索了其他几种方法:

I have explored several other approaches with Win32 API calls:

它们都存在完全相同的问题(不足为奇,因为我想它们的内部工作还是要与.net等效的Keyboard.GetKeyStates共享).

And they all have the exact same issue (not surprising as I suppose their inner workings are shared with their .net equivalent Keyboard.GetKeyStates anyway).

现在我正在寻找的是一种随时知道NumPad2键是否真正按下的方法.

Now what I'm looking for is a way at any moment to actually know if that NumPad2 key is really pressed...

推荐答案

因此,问题归结为Windows报告按键方式的错误.

So the issue comes down to a bug in the way Windows reports key presses.

Windows在称为虚拟键盘的物理键盘上创建了一个抽象层.虚拟键盘侦听物理键盘事件,并使用其维护键的状态,以后可以通过Windows API调用(其中User32.dll调用GetKeyState,GetAsyncKeyState和GetKeyboardState)或包装这些API调用的.NET调用来检索这些键的状态. (那些在System.Windows.Input.Keyboard命名空间中).

Windows creates a layer of abstraction over the physical keyboard called the virtual keyboard. The virtual keyboard listens to physical keyboard events and uses it to maintain a state of the keys that can later be retrieved by Windows API calls (amongst which User32.dll calls GetKeyState, GetAsyncKeyState and GetKeyboardState) or the .NET calls that wrap those API calls (those in the System.Windows.Input.Keyboard namespace).

在这种情况下,它收到NumPad2的KeyDown事件,但收到Down的KeyUp事件(这是NumPad2的 shifted 版本),因此NumPad2的状态会一直保持按下状态关注.

In this case, it receives a KeyDown event for NumPad2, but a KeyUp event for Down (which is the shifted version of NumPad2), so the state for NumPad2 stays pressed as far as those calls are concerned.

基本上,我找到的解决方法都是不依赖这些调用.

Basically the workarounds I found were all about not relying on those calls.

以下是我发现可以解决的一些变通方法:

Here are some workarounds I have found to work:

1.使用Windows API直接挂接到物理键盘事件.

这个想法是通过SetWindowsHookEx Win32 API调用使用到物理键盘的钩子.

The idea is to use a hook to the physical keyboard using the SetWindowsHookEx Win32 API call.

此方法的主要问题是API仅提供事件,不提供状态.您必须维护键盘所有键的状态才能正常工作.

The main issue with this approach is that the API only provides events, no state. You have to maintain your own state of all the keys of the keyboard for this to work.

2.使用DirectX.

我发现的另一个解决方案是使用DirectInput,它是DirectX的一部分.这为DirectX添加了一个依赖项(如果您的应用程序在Windows Vista之前不支持任何功能,那就很好了).此外,除了第三方库之外,没有从托管代码直接访问DirectX的功能(过去,Microsoft.NET包装器将DirectX的旧版本作为DirectX API的一部分,但该版本已经过时,并且无法与最新版本一起使用.NET). 您可以:

The other solution I have found is to use DirectInput, which is part of DirectX. This adds a dependency to DirectX (which is fine if your application does not support anything before Windows Vista). Furthermore, there is no direct access to DirectX from managed code except from third-party libraries (there used to be a Microsoft .NET wrapper around old versions of DirectX as part of the DirectX API but it is obsolete and would not work with recent versions of .NET). You can either:

  • 使用第三方.NET库(我已经使用一个这样的库SharpDX创建了一个有效的概念证明,该库似乎提供了与运行它的计算机上安装的DirectX版本无关的优点). /li>
  • 为此用途使用C ++ DirectX API创建C ++库.
  • use a third-party .NET library (I have created a working proof of concept with one such library, SharpDX, which seems to provide the advantage of being agnostic to the DirectX version installed on the machine it runs on).
  • create a C++ library using the C++ DirectX API, for this specific use.

这篇关于Keyboard.GetKeyStates误报,我如何才能真正知道是否按下了某个键?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆