Windows键盘消息
最近做的表情工具会和各种聊天或者文本处理的软件打交道,表情的上屏就需要模拟键盘按键,所以这里简单介绍下Windows
的键盘消息。
虚拟键
键盘上每一个键对应一个扫描码,扫描码是OEM
厂商制定的,不同的厂商生产的键盘同样一个按键的扫描码都有可能出现不一致的情况,为了摆脱由于系统设备不一致的情况,通过键盘驱动程序将扫描码映射为统一的虚拟键码表示,从而达到所有的设备都有一个统一的虚拟键,比如回车键的虚拟键是VK_RETURN
。
Windows
定义的虚拟键都定义在WinUser.
h这个头文件里面,都是以VK_
为前缀。
键盘消息
字符消息
系统字符消息:
WM_SYSCHAR:
系统字符WM_SYSDEADCHAR:
系统死字符
非系统按键消息:
WM_CHAR:
非系统字符WM_DEADCHAR:
非系统死字符
按键消息
系统按键消息:
WM_SYSKEYDOWN
WM_SYSKEYUP
PS:系统按键就是与ALT键相组合的组合键,无论用户处理否,都需要最后调用DefWindowProc(hWnd,iMessage,wParam,lParam)
。
非系统按键消息:
WM_KEYDOWN
WM_KEYUP
注意:
- 除
Print
键之外都有WM_KEYDOWN
消息。 - 所有键都存在
WM_KEYUP
消息。 - 根据
MSDN
说明,只有下面这些键才会产生字符消息:a)
任何字符键b
) 回退键(BACKSPACE
)c)
回车键(carriage return
)d)
ESC
e)
HIFT + ENTER
(linefeed 换行)f)
TAB
我们是怎么收到WM_CHAR
的呢?就是因为我们在消息循环时调用了TranslateMessage
对键盘消息进行翻译,
如果消息为WM_KEYDOWN
或者WM_SYSKEYDOWN
,并且按键与位移状态相组合产生一个字符,则TranslateMessage
把字符消息放入消息队列中。此字符消息将是GetMessage
从消息队列中得到的按键消息之后的下一个消息。
在我们处理这个消息时,对应的wParam
不是虚拟键,而是ANSI
或Unicode
字符代码,一般情况下我们可以这样用: (TCHAR)wParam
消息顺序
因为TranslateMessage
函数从WM_KEYDOWN
和WM_SYSKEYDOWN
消息产生了字符消息,所以字符消息是夹在按键消息之间传递给窗口消息处理程序的。
例如,如果Caps Lock
未打开,而使用者按下再释放A
键,则窗口消息处理程序将依次接收到下面三个消息:
消息 | 按键或者代码 |
---|---|
WM_KEYDOWN |
「A」的虚拟键码(0x41) |
WM_CHAR |
「a」的字符代码(0x61) |
WM_KEYUP |
「A」的虚拟键码(0x41) |
如果您按下Shift
键,再按下A
键,然后释放A
键,再释放Shift
键,就会输入大写的A
,而窗口消息处理程序会依次接收到五个消息:
消息 | 按键或者代码 |
---|---|
WM_KEYDOWN |
虚拟键码VK_SHIFT (0x10) |
WM_KEYDOWN |
「A」的虚拟键码(0x41) |
WM_CHAR |
「a」的字符代码(0x61) |
WM_KEYUP |
「A」的虚拟键码(0x41) |
WM_KEYUP |
虚拟键码VK_SHIFT (0x10) |
我们一般可以这样处理WM_CARH消息:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29ase WM_CHAR:
{
switch (wParam)
{
case 0x08:
// Process a backspace.
break;
case 0x0A:
// Process a linefeed.
break;
case 0x1B:
// Process an escape.
break;
case 0x09:
// Process a tab.
break;
case 0x0D:
// Process a carriage return.
break;
default:
// Process displayable characters.
break;
}
}
我们可以在WM_CHAR
里面判断当前是否有指定的键被按下:1
2
3
4// MFC源码 afxcolordialog.cpp 460行
BOOL bIsCtrl = (::GetAsyncKeyState(VK_CONTROL) & 0x8000);
// 或
BOOL bIsCtrl = (::GetKeyState(VK_CONTROL) & 0x8000);
下面我解释一下键盘消息的lParam
参数,这个参数在MSDN
上面都可以查到,只是英文,我这里作一些简单的说明(以WM_KEYDOWN
为例):
WPARAM
:虚拟键值,VT_*等值。
LPARAM
:根据其不同的位数表示的含义不同可以分以下几部分:
(1) 重复计数位(0 - 15 位)
: 表示消息按键数据。一般情况下为1
,当键一直按下,窗口过程就会连续收到W_KEYDOWN
消息,但有可能窗口过程来不及处理这些按键消息,那么Windows
就会把几个按键消息组合成一个,并增加重复计数。比如你处理WM_KEYDOWN
时Sleep(200)
,那么得到的这个数字就可能大于1
,一般可以这样来得到这个计数:1
DWORD count = (((DWORD)lParam) & 0x0000FFFF);
(2) OEM
扫描码(16~23位)
: OEM扫描码是键盘发送的码值,由于此域是设备相关的,因而此值往往被忽略。
(3) 扩展键标志(24位)
: 扩展键标志在有Alt
键(或Ctrl
键)按下时为1
,否则为0
。
(4) 保留位(25~28位)
: 保留位是系统缺省保留的,一般不用。
(5) 关联码(29位)
: 关联码用来记录某键与Alt
键的组合状态,若按下Alt
,当WM_SYSKEYDOWN
消息送到某个激活
的窗口时,其值为1
,否则为0
。
(6) 键的先前状态(位30)
: 键的先前状态用于记录先前某键的状态,对于WM_SYSKEYUP
消息,其值始终为1
。
(7) 转换状态(31位)
: 转换状态的消息是始终按着某键所产生的消息,若某键原来是按下的,则其先前状态为0
。转换状态指示键被按下还是被松开。当键被按下时,对应于者WM_SYSKEYDOWN
消息,其值始终为0
,当键被松开时,其转换状态为1
,对应于WM_SYSKEYUP
消息,其值始终为1
。
注意事项与问题
这里的问题就是,我们表情工具在用户点击表情上屏后。用户的习惯应该是在被上屏的程序及能直接响应输入或者回车之类的键盘操作,所以我们就需要再上屏后把焦点给被上屏的程序。这里是没问题,问题就在我们给出焦点后,用户在我们表情工具页面的上鼠标滚动事件就不能响应(Win10可以)。
所以解决方案有两种:
- 焦点还给我们表情工具,然后把键盘消息重发给被上屏的程序;
- 焦点给被上屏的程序,我们想办法获取到鼠标滚动事件。
咋一看明显第一种简单,因为被上屏的程序可能是QQ、微信等等,别人的程序我们不好控制。但是问题就在这,Windows
有个机制,键盘消息只发给激活窗口,就算你手动发键盘消息给一个非激活窗口对方也接收不到。
所以现在只剩下第二种,目前我能想到就是全局HOOK
,大家如果有更好的解决办法,请告诉我。