问题
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:
- GetKeyState
- GetKeyboardState
- GetAsyncKeyStates
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...
回答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.
来源:https://stackoverflow.com/questions/26159752/keyboard-getkeystates-false-positive-how-do-i-actually-know-if-a-key-is-pressed