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:
- press NumPad2
- press LShift
- release NumPad2
- 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...