Keyboard.GetKeyStates false positive, how do I really know if a key is pressed?

There seems to be a way Keyboard.GetKeyStates

to return incorrectly pressed keys, for example to Keyboard.GetKeyStates(Key.NumPad2)

return Down, Toggled

even if not pressed.

I was able to reproduce this in a very simple WPF Key Events application. The way to reproduce the error is as follows:

  • press NumPad2
  • press the LShift key
  • release NumPad2
  • release LShift

From now on, the status check for NumPad2 will always give Down, Toggled

until you press and release it again.

Not sure if this matters, but I'm using the UK enhanced English keyboard on Windows 8.1 x64

The reason is that LShift-NumPad2 is actually equivalent to the Down key (makes sense), but Windows doesn't seem to understand that this means NumPad2 is no longer pressed.

My test app just grabs KeyDown and KeyUp and shows me the KeyStates changes for each event, as well as a list of KeyStates for the entire keyboard after each event (I compare it to the state when the app started so that it doesn't pollute the output with the state of NumLock keys and others).

This is the result 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

      

So, as you can see, the NumPad2 key appears as pressed after steps 3 and 4, even though I released it in step 3.

Here is the complete code for the xaml and code behind if you want to copy / paste this directly 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>

      

and

   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]);
         }
      }
   }

      

I've looked at several other approaches to Win32 API calls:

And they all have the same problem (unsurprisingly, since I believe their inner workings are shared with their .net equivalent to Keyboard.GetKeyStates anyway).

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

+3


source to share


1 answer


So the problem boils down to an error in the way the keys are pressed by keystrokes.

Windows creates a layer of abstraction above the physical keyboard called the virtual keyboard. The virtual keyboard listens for physical keyboard events and uses it to maintain the state of the keys, which can subsequently be invoked by Windows API calls (among which User32.dll calls GetKeyState, GetAsyncKeyState, and GetKeyboardState) or .NET calls that wrap these API calls (in the System .Windows.Input.Keyboard).

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

Most of the workarounds I found were not about not relying on these calls.

Here are some workarounds I've found to work:

1. Connect directly to physical keyboard events using the Windows API.



The idea is to bind to the physical keyboard using the SetWindowsHookEx Win32 API call.

The main problem with this approach is that the API only exposes events, not state. To do this, you need to maintain their own state of all keyboard keys.

2. Use DirectX.

Another solution I found is to use DirectInput, which is included with DirectX. This adds a dependency on DirectX (this is fine if your application doesn't support anything prior to Windows Vista). Also, there is no direct access to DirectX from managed code other than from third party libraries (there used to be a Microsoft .NET wrapper around older versions of DirectX as part of the DirectX API, but it is deprecated and won't work with the latest versions of .NET). You can:

  • use a third party .NET library (I've created a working proof of concept with one such library SharpDX, which seems to have the advantage of being agnostic for the DirectX version installed on the machine it runs on).
  • create a C ++ library using the C ++ DirectX API, for this specific use.
0


source







All Articles