文章目录
  1. 1. 虚拟键
  2. 2. 键盘消息
    1. 2.1. 字符消息
    2. 2.2. 按键消息
  3. 3. 消息顺序
  4. 4. 注意事项与问题

最近做的表情工具会和各种聊天或者文本处理的软件打交道,表情的上屏就需要模拟键盘按键,所以这里简单介绍下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不是虚拟键,而是ANSIUnicode字符代码,一般情况下我们可以这样用: (TCHAR)wParam

消息顺序

因为TranslateMessage函数从WM_KEYDOWNWM_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
29
ase 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_KEYDOWNSleep(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,大家如果有更好的解决办法,请告诉我。

文章目录
  1. 1. 虚拟键
  2. 2. 键盘消息
    1. 2.1. 字符消息
    2. 2.2. 按键消息
  3. 3. 消息顺序
  4. 4. 注意事项与问题