【发布时间】:2014-10-02 11:24:13
【问题描述】:
Keyboard.GetKeyStates 似乎有一种方法可以返回错误按下的键,例如,Keyboard.GetKeyStates(Key.NumPad2) 即使没有按下也可以返回Down, Toggled。
我已经能够在一个非常简单的 WPF 应用程序上重现这一点,该应用程序捕获 keyUp 事件。 重现bug的方法如下:
- 按 NumPad2
- 按 LShift
- 释放 NumPad2
- 释放 LShift
从那时起,检查 NumPad2 的状态将始终返回 Down, Toggled,直到您再次按下并释放它。
不确定是否重要,但我在 Windows 8.1 x64
上使用英语英国扩展键盘原因似乎是 LShift-NumPad2 实际上等同于 Down 键(有道理),但 Windows 似乎没有意识到这意味着 NumPad2 不再被按下。
我的测试应用程序只是捕获 KeyDown 和 KeyUp,并向我显示每个事件的 KeyStates 更改以及每个事件后整个键盘的 KeyStates 列表(我将它与应用程序启动时的状态进行比较,以免使用 NumLock 键和其他键的状态污染输出。
这是我在之前的测试中得到的输出:
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
所以你可以看到 NumPad2 键在第 3 步和第 4 步之后显示为按下,尽管我在第 3 步释放了它。
这里是 xaml 的完整代码和后面的代码,以防您想将其直接复制/粘贴到新项目中并希望看到它的实际效果:
<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 调用的方法:
而且它们都有完全相同的问题(这并不奇怪,因为我认为它们的内部工作原理与它们的 .net 等效 Keyboard.GetKeyStates 共享)。
现在我正在寻找的是一种随时知道 NumPad2 键是否真的被按下的方法......
【问题讨论】:
-
请写Windows版本,包括(x64/x32)和.NET版本。例如,类似的问题出现在 Windows 98 中,但在 Win XP 中不存在。
-
键盘状态由系统维护每个线程,只要线程从输入队列中检索消息。以这种方式维护的状态会受到可访问性增强(例如粘滞键)的影响。如果要确定密钥的物理状态,则需要进入系统的状态管理之下。例如,使用DirectInput,您可以查询硬件驱动程序以获取原始输入数据。
-
@IInspectable 非常有用的见解。您应该将您的评论扩展到答案。
-
@IInspectable 我对 DirectX 完全不熟悉。我刚刚花了一定时间尝试从我的项目中引用 DirectInput,但我不知道如何!
-
这是您必须处理的 Windows 怪癖。从键盘生成的扫描码到您看到的虚拟键码的转换完全基于当前键盘状态。没有“记忆”先前生成的虚拟键码。如果您想知道某个键是否已关闭,则只需检索其状态,不要自己缓冲该状态。 GetAsyncKeyState() 值得注意,很少合适。
标签: c# .net wpf winapi keyboard-events