4

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.

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. press NumPad2
  2. press LShift
  3. release NumPad2
  4. release LShift

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

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

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.

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

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

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>

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 have explored several other approaches with Win32 API calls:

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).

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

Evren Kuzucuoglu
  • 3,781
  • 28
  • 51
  • Please write the Windows version including (x64/x32) and .NET version. The similar problem for example was in Windows 98, but was not present in Win XP. – Alex Blokha Oct 02 '14 at 11:50
  • 3
    Keyboard state is maintained per thread by the system, whenever a thread retrieves a message from the input queue. State maintained in this way is subject to accessibility enhancements (e.g. sticky keys). If you want to determine the physical state of a key you need to go underneath the system's state management. Using [DirectInput](http://msdn.microsoft.com/en-us/library/windows/desktop/ee416842.aspx), for example, gives you the ability to query hardware drivers for raw input data. – IInspectable Oct 02 '14 at 12:07
  • @IInspectable Very useful insights. You should extend your comment into an answer. – Mike Strobel Oct 02 '14 at 14:55
  • @IInspectable I am absolutely not familiar with DirectX. I have just spent a certain amount of time trying to reference DirectInput from my project but I have no idea how! – Evren Kuzucuoglu Oct 02 '14 at 16:00
  • This is a Windows quirk that you'll have to deal with. The translation from the scan code that the keyboard generated to the virtual key code that you see is based purely on the current keyboard state. There is no "memory" of what virtual key code was generated previously. If you want to know if a key is down then just retrieve its status, don't buffer that status yourself. GetAsyncKeyState() is notable, rarely appropriate. – Hans Passant Oct 02 '14 at 16:58

1 Answers1

1

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

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).

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. Directly hook into the physical keyboards events using the Windows API.

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

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. Use DirectX.

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:

  • 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.
Evren Kuzucuoglu
  • 3,781
  • 28
  • 51