键盘输入概述

应用程序应接受来自键盘和鼠标的用户输入。 应用程序以发布到其窗口的消息的形式接收键盘输入。

键盘输入模型

系统通过安装适用于当前键盘的键盘设备驱动程序,为应用程序提供与设备无关的键盘支持。 系统通过使用用户或应用程序当前选择的特定于语言的键盘布局,提供与语言无关的键盘支持。 键盘设备驱动程序从键盘接收扫描代码,这些代码将发送到键盘布局,在键盘布局中,扫描代码将转换为消息并发布到应用程序中的相应窗口。

分配给键盘上每个键的唯一值称为扫描代码,是键盘上键的设备相关标识符。 当用户键入某个键时,键盘会生成两个扫描代码,一个在用户按下该键时生成,另一个在用户松开该键时生成。

键盘设备驱动程序解释扫描代码并将其转换(映射)为虚拟键代码,这是一个由系统定义的独立于设备的值,用于标识键的用途。 转换扫描代码后,键盘布局会创建一条消息,其中包含扫描代码、虚拟键代码和有关击键的其他信息,然后将该消息置于系统消息队列中。 系统将该消息从系统消息队列中删除,并将其发布到相应线程的消息队列。 最终,线程的消息循环会删除该消息,并将其传递给相应的窗口过程进行处理。 下图演示了键盘输入模型。

keyboard input processing model

键盘焦点和激活

系统将键盘消息发布到创建具有键盘焦点的窗口的前台线程的消息队列。 键盘焦点是窗口的临时属性。 系统将键盘焦点从一个窗口移动到另一个窗口,从而在显示器上的所有窗口之间共享键盘。 具有键盘焦点的窗口(从创建它的线程的消息队列)接收所有键盘消息,直到焦点更改为其他窗口。

线程可以调用 GetFocus 函数来确定哪个窗口(如果有)当前具有键盘焦点。 线程可以通过调用 SetFocus 函数将键盘焦点赋予其中一个窗口。 将键盘焦点从一个窗口更改为另一个窗口时,系统会向失去焦点的窗口发送 WM_KILLFOCUS 消息,然后向获得焦点的窗口发送 WM_SETFOCUS 消息

键盘焦点的概念与活动窗口的概念相关。 活动窗口是用户当前正在使用的顶级窗口。 具有键盘焦点的窗口要么是活动窗口,要么是活动窗口的子窗口。 为了帮助用户识别活动窗口,系统会将其置于 Z 顺序的顶部并突出显示其标题栏(如果有)和边框。

用户可以通过单击顶级窗口、使用 ALT+TAB 或 Alt+ESC 组合键选择它或从“任务列表”中选择它来激活该窗口。 线程可以使用 SetActiveWindow 函数激活顶级窗口。 它可以使用 GetActiveWindow 函数确定所创建的顶级窗口是否处于活动状态

当一个窗口停用而另一个窗口被激活时,系统会发送 WM_ACTIVATE 消息。 如果正在停用窗口,则 wParam 参数的低序字为零,如果正在激活窗口,则为非零。 默认窗口过程收到 WM_ACTIVATE 消息后,便会将键盘焦点设置到活动窗口

若要阻止键盘和鼠标输入事件到达应用程序,请使用 BlockInput。 请注意,BlockInput 函数不会干扰异步键盘输入状态表。 这意味着在输入被阻止时调用 SendInput 函数将更改异步键盘输入状态表

击键消息

按下键会使 WM_KEYDOWNWM_SYSKEYDOWN 消息被放入附加到具有键盘焦点的窗口的线程消息队列中。 松开键会使 WM_KEYUPWM_SYSKEYUP 消息被放入队列中

按下键和松开键消息通常是相伴发生的,但如果用户长按一个键,以致键盘的自动重复功能启动,则系统会连续生成许多 WM_KEYDOWNWM_SYSKEYDOWN 消息。 然后,当用户松开键时,它会生成一条 WM_KEYUPWM_SYSKEYUP 消息

本部分涵盖了以下主题:

系统和非系统击键

系统区分系统击键和非系统击键。 系统击键会生成系统击键消息 WM_SYSKEYDOWNWM_SYSKEYUP。 非系统击键会生成非系统击键消息 WM_KEYDOWNWM_KEYUP

如果窗口过程必须处理系统击键消息,请确保在处理消息后,过程将其传递给 DefWindowProc 函数。 否则,只要窗口具有键盘焦点,所有涉及 ALT 键的系统操作都将被禁用。 也就是说,用户将无法访问窗口的菜单或系统菜单,或使用 Alt+ESC 或 ALT+TAB 组合键激活其他窗口。

系统击键消息主要供系统而不是应用程序使用。 系统使用它们向菜单提供其内置键盘界面,并允许用户控制哪个窗口处于活动状态。 当用户键入一个键并同时键入 ALT 键时,或者当用户键入且没有窗口具有键盘焦点时(例如,活动应用程序最小化时),将生成系统击键消息。 在这种情况下,消息将发布到附加到活动窗口的消息队列。

非系统击键消息供应用程序窗口使用;DefWindowProc 函数不对其执行任何操作。 窗口过程可以丢弃它不需要的任何非系统击键消息。

描述的虚拟键代码

击键消息的 wParam 参数包含按下或松开的键的虚拟键代码。 窗口过程根据虚拟键代码的值处理或忽略击键消息。

典型的窗口过程仅处理它接收到的击键消息的一小部分,而忽略其余部分。 例如,窗口过程可能仅处理 WM_KEYDOWN 击键消息,并且仅处理那些包含光标移动键、Shift 键(也称为控制键)和功能键的虚拟键代码的消息。 典型的窗口过程不处理来自字符键的击键消息。 相反,它使用 TranslateMessage 函数将消息转换为字符消息。 有关 TranslateMessage 和字符消息的详细信息,请参阅字符消息

击键消息标志

击键消息的 lParam 参数包含有关生成该消息的击键的附加信息。 此信息包括重复计数扫描代码扩展键标志上下文代码上一个键状态标志转换状态标志。 下图显示了这些标志和值在 lParam 参数中的位置

the locations of the flags and values in the lparam parameter of a keystroke message

应用程序可以使用以下值从 lParam 的高位字中获取击键标志

说明
KF_EXTENDED
0x0100
操作扩展键标志
KF_DLGMODE
0x0800
操作对话框模式标志,该标志指示对话框是否处于活动状态。
KF_MENUMODE
0x1000
操作菜单模式标志,该标志指示菜单是否处于活动状态。
KF_ALTDOWN
0x2000
操作上下文代码标志
KF_REPEAT
0x4000
操作上一个键状态标志
KF_UP
0x8000
操作转换状态标志

示例代码:

case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
{
    WORD vkCode = LOWORD(wParam);                                 // virtual-key code
    
    WORD keyFlags = HIWORD(lParam);

    WORD scanCode = LOBYTE(keyFlags);                             // scan code
    BOOL isExtendedKey = (keyFlags & KF_EXTENDED) == KF_EXTENDED; // extended-key flag, 1 if scancode has 0xE0 prefix
    
    if (isExtendedKey)
        scanCode = MAKEWORD(scanCode, 0xE0);

    BOOL wasKeyDown = (keyFlags & KF_REPEAT) == KF_REPEAT;        // previous key-state flag, 1 on autorepeat
    WORD repeatCount = LOWORD(lParam);                            // repeat count, > 0 if several keydown messages was combined into one message

    BOOL isKeyReleased = (keyFlags & KF_UP) == KF_UP;             // transition-state flag, 1 on keyup

    // if we want to distinguish these keys:
    switch (vkCode)
    {
    case VK_SHIFT:   // converts to VK_LSHIFT or VK_RSHIFT
    case VK_CONTROL: // converts to VK_LCONTROL or VK_RCONTROL
    case VK_MENU:    // converts to VK_LMENU or VK_RMENU
        vkCode = LOWORD(MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK_EX));
        break;
    }

    // ...
}
break;

重复计数

可以检查重复计数,以确定一个击键消息是否表示多个击键。 当键盘生成 WM_KEYDOWNWM_SYSKEYDOWN 消息的速度快于应用程序处理它们的速度时,系统会增加计数。 当用户长按某个键,以致键盘的自动重复功能启动时,通常会发生这种情况。 系统不会将生成的按键消息填充到系统消息队列中,而是将消息组合成单个按键消息并增加重复计数。 松开键无法启动自动重复功能,因此 WM_KEYUPWM_SYSKEYUP 消息的重复计数始终设置为 1

扫描代码

Diagram of a Type 4 keyboard with the key locations for each key.

扫描代码是系统在用户按下某个键时生成的值。 它是一个值,该值标识按下的键(不考虑活动键盘布局),而不是由键表示的字符。 应用程序通常忽略扫描代码。 它使用虚拟键代码来解释击键消息。

新式键盘使用人机接口设备 (HID) 规范与计算机通信。 键盘驱动程序转换从键盘发送的已报告 HID 使用值以扫描代码并将其传递到应用程序。

注意

虽然虚拟键代码通常对桌面应用程序更有用,但在需要知道按下哪个键(不考虑当前的键盘布局)的特殊情况下,可能需要使用扫描代码。 例如,用于游戏的 WASD(W 向上、A 向左、S 向下和 D 向右)键绑定,这可确保跨 US QWERTYFrench AZERTY 键盘布局的一致键结构。

下表列出了 Windows 目前识别的扫描代码集。 HID 使用页面/HID 使用 ID/HID 使用名称值引用 HID 使用表文档。 键位置值引用前面的键盘图像

Scan 1 Make 代码在 WM_KEYDOWN/WM_KEYUP/WM_SYSKEYDOWN/WM_SYSKEYUPWM_INPUT 消息中传递。

HID 使用页面名称 HID 用法名称 HID 使用页面 HID 用法 ID Scan 1 Make 键位置
通用桌面 系统断电 0x0001 0x0081 0xE05E
通用桌面 系统睡眠 0x0001 0x0082 0xE05F
通用桌面 系统唤醒 0x0001 0x0083 0xE063
键盘/小键盘 ErrorRollOver 0x0007 0x0001 0x00FF
键盘/小键盘 键盘 A 0x0007 0x0004 0x001E 31
键盘/小键盘 键盘 B 0x0007 0x0005 0x0030 50
键盘/小键盘 键盘 C 0x0007 0x0006 0x002E 48
键盘/小键盘 键盘 D 0x0007 0x0007 0x0020 33
键盘/小键盘 键盘 E 0x0007 0x0008 0x0012 19
键盘/小键盘 键盘 F 0x0007 0x0009 0x0021 34
键盘/小键盘 键盘 G 0x0007 0x000A 0x0022 35
键盘/小键盘 键盘 H 0x0007 0x000B 0x0023 36
键盘/小键盘 键盘 I 0x0007 0x000C 0x0017 24
键盘/小键盘 键盘 J 0x0007 0x000D 0x0024 37
键盘/小键盘 键盘 K 0x0007 0x000E 0x0025 38
键盘/小键盘 键盘 L 0x0007 0x000F 0x0026 39
键盘/小键盘 键盘 M 0x0007 0x0010 0x0032 52
键盘/小键盘 键盘 N 0x0007 0x0011 0x0031 51
键盘/小键盘 键盘 O 0x0007 0x0012 0x0018 25
键盘/小键盘 键盘 P 0x0007 0x0013 0x0019 26
键盘/小键盘 键盘 Q 0x0007 0x0014 0x0010 17
键盘/小键盘 键盘 R 0x0007 0x0015 0x0013 20
键盘/小键盘 键盘 S 0x0007 0x0016 0x001F 32
键盘/小键盘 键盘 T 0x0007 0x0017 0x0014 21
键盘/小键盘 键盘 U 0x0007 0x0018 0x0016 23
键盘/小键盘 键盘 V 0x0007 0x0019 0x002F 49
键盘/小键盘 键盘 W 0x0007 0x001A 0x0011 18
键盘/小键盘 键盘 X 0x0007 0x001B 0x002D 47
键盘/小键盘 键盘 Y 0x0007 0x001C 0x0015 22
键盘/小键盘 键盘 Z 0x0007 0x001D 0x002C 46
键盘/小键盘 键盘 1 和 ! 0x0007 0x001E 0x0002 2
键盘/小键盘 键盘 2 和 @ 0x0007 0x001F 0x0003 3
键盘/小键盘 键盘 3 和 # 0x0007 0x0020 0x0004 4
键盘/小键盘 键盘 4 和 $ 0x0007 0x0021 0x0005 5
键盘/小键盘 键盘 5 和 % 0x0007 0x0022 0x0006 6
键盘/小键盘 键盘 6 和 ^ 0x0007 0x0023 0x0007 7
键盘/小键盘 键盘 7 和 & 0x0007 0x0024 0x0008 8
键盘/小键盘 键盘 8 和 * 0x0007 0x0025 0x0009 9
键盘/小键盘 键盘 9 和 ( 0x0007 0x0026 0x000A 10
键盘/小键盘 键盘 0 和 ) 0x0007 0x0027 0x000B 11
键盘/小键盘 键盘回车 Enter 键 0x0007 0x0028 0x001C 43
键盘/小键盘 键盘 Escape 键 0x0007 0x0029 0x0001 110
键盘/小键盘 键盘 Delete 键 0x0007 0x002A 0x000E 15
键盘/小键盘 键盘 Tab 键 0x0007 0x002B 0x000F 16
键盘/小键盘 键盘空格键 0x0007 0x002C 0x0039 61
键盘/小键盘 键盘 - 和 _ 0x0007 0x002D 0x000C 12
键盘/小键盘 键盘 = 和 + 0x0007 0x002E 0x000D 13
键盘/小键盘 键盘 { 0x0007 0x002F 0x001A 27
键盘/小键盘 键盘 } 0x0007 0x0030 0x001B 28
键盘/小键盘 键盘 | 和 \ 0x0007 0x0031 0x002B 29
键盘/小键盘 键盘非美国 0x0007 0x0032 0x002B 42
键盘/小键盘 键盘 ; 和 : 0x0007 0x0033 0x0027 40
键盘/小键盘 键盘 ‘ 和 “ 0x0007 0x0034 0x0028 41
键盘/小键盘 键盘 ` 和 ~ 0x0007 0x0035 0x0029 1
键盘/小键盘 键盘 , 0x0007 0x0036 0x0033 53
键盘/小键盘 键盘 . 0x0007 0x0037 0x0034 54
键盘/小键盘 键盘 ? 0x0007 0x0038 0x0035 55
键盘/小键盘 键盘 Caps Lock 0x0007 0x0039 0x003A 30
键盘/小键盘 键盘 F1 0x0007 0x003A 0x003B 112
键盘/小键盘 键盘 F2 0x0007 0x003B 0x003C 113
键盘/小键盘 键盘 F3 0x0007 0x003C 0x003D 114
键盘/小键盘 键盘 F4 0x0007 0x003D 0x003E 115
键盘/小键盘 键盘 F5 0x0007 0x003E 0x003F 116
键盘/小键盘 键盘 F6 0x0007 0x003F 0x0040 117
键盘/小键盘 键盘 F7 0x0007 0x0040 0x0041 118
键盘/小键盘 键盘 F8 0x0007 0x0041 0x0042 119
键盘/小键盘 键盘 F9 0x0007 0x0042 0x0043 120
键盘/小键盘 键盘 F10 0x0007 0x0043 0x0044 121
键盘/小键盘 键盘 F11 0x0007 0x0044 0x0057 122
键盘/小键盘 键盘 F12 0x0007 0x0045 0x0058 123
键盘/小键盘 键盘 PrintScreen 键 0x0007 0x0046 0xE037
0x0054 *注意 1
124
键盘/小键盘 键盘 Scroll Lock 键 0x0007 0x0047 0x0046 125
键盘/小键盘 键盘 Pause 键 0x0007 0x0048 0xE11D45
0xE046 *注意 2
0x0045 *注意 3
126
键盘/小键盘 键盘 Insert 键 0x0007 0x0049 0xE052 75
键盘/小键盘 键盘 Home 键 0x0007 0x004A 0xE047 80
键盘/小键盘 键盘 PageUp 键 0x0007 0x004B 0xE049 85
键盘/小键盘 键盘 Delete Forward 键 0x0007 0x004C 0xE053 76
键盘/小键盘 键盘 End 键 0x0007 0x004D 0xE04F 81
键盘/小键盘 键盘 PageDown 键 0x0007 0x004E 0xE051 86
键盘/小键盘 键盘向右键 0x0007 0x004F 0xE04D 89
键盘/小键盘 键盘向左键 0x0007 0x0050 0xE04B 79
键盘/小键盘 键盘向下键 0x0007 0x0051 0xE050 84
键盘/小键盘 键盘向上键 0x0007 0x0052 0xE048 83
键盘/小键盘 小键盘 Num Lock 和清除键 0x0007 0x0053 0x0045
0xE045 *注意 3
90
键盘/小键盘 小键盘 / 0x0007 0x0054 0xE035 95
键盘/小键盘 小键盘 * 0x0007 0x0055 0x0037 100
键盘/小键盘 小键盘 - 0x0007 0x0056 0x004A 105
键盘/小键盘 小键盘 + 0x0007 0x0057 0x004E 106
键盘/小键盘 小键盘 ENTER 0x0007 0x0058 0xE01C 108
键盘/小键盘 小键盘 1 和 End 0x0007 0x0059 0x004F 93
键盘/小键盘 小键盘 2 和向下键 0x0007 0x005A 0x0050 98
键盘/小键盘 小键盘 3 和 PageDn 0x0007 0x005B 0x0051 103
键盘/小键盘 小键盘 4 和向左键 0x0007 0x005C 0x004B 92
键盘/小键盘 小键盘 5 0x0007 0x005D 0x004C 97
键盘/小键盘 小键盘 6 和向左键 0x0007 0x005E 0x004D 102
键盘/小键盘 小键盘 7 和 Home 0x0007 0x005F 0x0047 91
键盘/小键盘 小键盘 8 和向上键 0x0007 0x0060 0x0048 96
键盘/小键盘 小键盘 9 和 PageUp 0x0007 0x0061 0x0049 101
键盘/小键盘 小键盘 0 和 Insert 0x0007 0x0062 0x0052 99
键盘/小键盘 小键盘 . 0x0007 0x0063 0x0053 104
键盘/小键盘 键盘非美国斜杠 0x0007 0x0064 0x0056 45
键盘/小键盘 键盘应用程序键 0x0007 0x0065 0xE05D 129
键盘/小键盘 键盘电源键 0x0007 0x0066 0xE05E
键盘/小键盘 小键盘 = 0x0007 0x0067 0x0059
键盘/小键盘 键盘 F13 0x0007 0x0068 0x0064
键盘/小键盘 键盘 F14 0x0007 0x0069 0x0065
键盘/小键盘 键盘 F15 0x0007 0x006A 0x0066
键盘/小键盘 键盘 F16 0x0007 0x006B 0x0067
键盘/小键盘 键盘 F17 0x0007 0x006C 0x0068
键盘/小键盘 键盘 F18 0x0007 0x006D 0x0069
键盘/小键盘 键盘 F19 0x0007 0x006E 0x006A
键盘/小键盘 键盘 F20 0x0007 0x006F 0x006B
键盘/小键盘 键盘 F21 0x0007 0x0070 0x006C
键盘/小键盘 键盘 F22 0x0007 0x0071 0x006D
键盘/小键盘 键盘 F23 0x0007 0x0072 0x006E
键盘/小键盘 键盘 F24 0x0007 0x0073 0x0076
键盘/小键盘 小键盘 , 0x0007 0x0085 0x007E 107 *注意 4
键盘/小键盘 键盘 International1 0x0007 0x0087 0x0073 56 *注意 4、5
键盘/小键盘 键盘 International2 0x0007 0x0088 0x0070 133 *注意 5
键盘/小键盘 键盘 International3 0x0007 0x0089 0x007D 14 *注意 5
键盘/小键盘 键盘 International4 0x0007 0x008A 0x0079 132 *注意 5
键盘/小键盘 键盘 International5 0x0007 0x008B 0x007B 131 *注意 5
键盘/小键盘 键盘 International6 0x0007 0x008C 0x005C
键盘/小键盘 键盘 LANG1 0x0007 0x0090 0x0072 *注意 6
0x00F2 *注意 3、6
键盘/小键盘 键盘 LANG2 0x0007 0x0091 0x0071 *注意 6
0x00F1 *注意 3、6
键盘/小键盘 键盘 LANG3 0x0007 0x0092 0x0078
键盘/小键盘 键盘 LANG4 0x0007 0x0093 0x0077
键盘/小键盘 键盘 LANG5 0x0007 0x0094 0x0076
键盘/小键盘 键盘 LeftControl 0x0007 0x00E0 0x001D 58
键盘/小键盘 键盘 LeftShift 0x0007 0x00E1 0x002A 44
键盘/小键盘 键盘 LeftAlt 0x0007 0x00E2 0x0038 60
键盘/小键盘 键盘 Left GUI 0x0007 0x00E3 0xE05B 127
键盘/小键盘 键盘 RightControl 0x0007 0x00E4 0xE01D 64
键盘/小键盘 键盘 RightShift 0x0007 0x00E5 0x0036 57
键盘/小键盘 键盘 RightAlt 0x0007 0x00E6 0xE038 62
键盘/小键盘 键盘 Right GUI 0x0007 0x00E7 0xE05C 128
消费者 扫描下一个曲目 0x000C 0x00B5 0xE019
消费者 扫描上一个曲目 0x000C 0x00B6 0xE010
消费者 停止 0x000C 0x00B7 0xE024
消费者 播放/暂停 0x000C 0x00CD 0xE022
消费者 静音 0x000C 0x00E2 0xE020
消费者 音量增大 0x000C 0x00E9 0xE030
消费者 音量减小 0x000C 0x00EA 0xE02E
消费者 AL 使用者控制配置 0x000C 0x0183 0xE06D
消费者 AL 电子邮件阅读器 0x000C 0x018A 0xE06C
消费者 AL 计算器 0x000C 0x0192 0xE021
消费者 AL 本地计算机浏览器 0x000C 0x0194 0xE06B
消费者 AC 搜索 0x000C 0x0221 0xE065
消费者 AC 开始 0x000C 0x0223 0xE032
消费者 AC 返回 0x000C 0x0224 0xE06A
消费者 AC 向前 0x000C 0x0225 0xE069
消费者 AC 停止 0x000C 0x0226 0xE068
消费者 AC 刷新 0x000C 0x0227 0xE067
消费者 AC 书签 0x000C 0x022A 0xE066

注释:

  1. Alt+Print screen 击键时发出 SysRq 键扫描代码
  2. Control+Pause 击键时发出 Break 键扫描代码
  3. 旧版键盘消息所示
  4. 键存在于巴西键盘上
  5. 键存在于日本键盘上
  6. 仅键释放事件中发出扫描代码

扩展键标志

扩展键标志指示击键消息是否源自增强型 101/102 键键盘上的一个附加键。 扩展键包括键盘右侧的 ALT 和 CTRL 键;INS、DEL、HOME、END、PAGE UP、PAGE DOWN 和数字键盘左侧键群中的方向键;NUM LOCK 键;BREAK (CTRL+PAUSE) 键;PRINT SCRN 键;以及数字键盘中的斜杠 (/) 和 ENTER 键。 右侧 SHIFT 键不被视为扩展键,它有一个单独的扫描代码。

如果指定,则扫描代码由两个字节的序列组成,其中第一个字节的值为 0xE0。

上下文代码

上下文代码指示生成击键消息时是否按下 ALT 键。 如果按下 ALT 键则代码为 1,如果不按则为 0。

上一个键状态标志

上一个键状态标志指示生成击键消息的键先前是否被按下。 如果键之前被按下,则为 1;如果键之前未按下,则为 0。 可以使用此标志来标识键盘的自动重复功能生成的击键消息。 对于自动重复功能生成的 WM_KEYDOWNWM_SYSKEYDOWN 击键消息,此标志设置为 1。 对于 WM_KEYUPWM_SYSKEYUP 消息,它始终设置为 1

转换状态标志

转换状态标志指示是按下键还是松开键生成击键消息。 对于 WM_KEYDOWNWM_SYSKEYDOWN 消息,此标志始终设置为 0;对于 WM_KEYUPWM_SYSKEYUP 消息,它始终设置为 1

字符消息

击键消息提供有关击键的大量信息,但它们不提供字符击键的字符代码。 若要检索字符代码,应用程序必须在其线程消息循环中包含 TranslateMessage 函数。 TranslateMessage 将 WM_KEYDOWNWM_SYSKEYDOWN 消息传递到键盘布局。 布局检查消息的虚拟键代码,如果它对应于字符键,则提供等效的字符代码(考虑到 SHIFT 和 CAPS LOCK 键的状态)。 然后,它会生成包含字符代码的字符消息,并将消息置于消息队列的顶部。 消息循环的下一次迭代会从队列中删除字符消息,并将消息调度到相应的窗口过程。

本部分涵盖了以下主题:

非系统字符消息

窗口过程可以接收以下字符消息:WM_CHARWM_DEADCHARWM_SYSCHARWM_SYSDEADCHARWM_UNICHARTranslateMessage 函数在处理 WM_KEYDOWN 消息时会生成 WM_CHAR 或 WM_DEADCHAR 消息。 同样,它在处理 WM_SYSKEYDOWN 消息时会生成 WM_SYSCHAR 或 WM_SYSDEADCHAR 消息

处理键盘输入的应用程序通常会忽略除 WM_CHARWM_UNICHAR 消息之外的所有消息,将任何其他消息传递给 DefWindowProc 函数。 请注意,WM_CHAR 使用 UTF-16(16 位 Unicode 转换格式)或 ANSI 字符集,而 WM_UNICHAR 始终使用 UTF-32(32 位 Unicode 转换格式)。 系统使用 WM_SYSCHARWM_SYSDEADCHAR 消息来实现菜单助记键

所有字符消息的 wParam 参数包含按下的字符键的字符代码。 字符代码的值取决于接收消息的窗口的窗口类。 如果使用 RegisterClass 函数的 Unicode 版本注册窗口类,系统会为该类的所有窗口提供 Unicode 字符。 否则,系统会提供 ANSI 字符代码。 有关详细信息,请参阅注册窗口类在 Windows 应用中使用 UTF-8 代码页

字符消息的 lParam 参数的内容与被转换为生成字符消息的按键消息的 lParam 参数的内容相同。 有关信息,请参阅击键消息标志

死字符消息

某些非英语键盘包含不应自行生成字符的字符键。 相反,它们用于向后续击键生成的字符添加变音符。 这些键称为死键。 德语键盘上的长音符键是死键的一个示例。 若要输入包含带长音符的“o”的字符,德国用户会键入长音符键,然后键入“o”键。 具有键盘焦点的窗口将收到以下消息序列:

  1. WM_KEYDOWN
  2. WM_DEADCHAR
  3. WM_KEYUP
  4. WM_KEYDOWN
  5. WM_CHAR
  6. WM_KEYUP

TranslateMessage 在处理来自死键的 WM_KEYDOWN 消息时生成 WM_DEADCHAR 消息。 尽管 WM_DEADCHAR 消息的 wParam 参数包含死键变音符的字符代码,但应用程序通常会忽略该消息。 相反,它会处理后续击键生成的 WM_CHAR 消息。 WM_CHAR 消息的 wParam 参数包含带有变音符的字母的字符代码。 如果后续击键生成的字符不能与变音符组合,系统会生成两条 WM_CHAR 消息。 第一个 wParam 参数包含变音符的字符代码;第二个 wParam 参数包含后续字符键的字符代码

TranslateMessage 函数在处理来自系统死键(与 ALT 键一起按下的死键)的 WM_SYSKEYDOWN 消息时生成 WM_SYSDEADCHAR 消息。 应用程序通常会忽略 WM_SYSDEADCHAR 消息

密钥状态

在处理键盘消息时,应用程序可能需要确定除生成当前消息的那个键之外的另一个键的状态。 例如,允许用户按 SHIFT+END 选择文本块的文字处理应用程序必须在收到来自 END 键的击键消息时检查 SHIFT 键的状态。 应用程序可以使用 GetKeyState 函数来确定生成当前消息时虚拟键的状态;它可以使用 GetAsyncKeyState 函数来检索虚拟键的当前状态

键盘布局保留名称列表。 生成单个字符的键的名称与键生成的字符的名称相同。 非字符键(如 TAB 和 ENTER)的名称存储为字符串。 应用程序可以通过调用 GetKeyNameText 函数从设备驱动程序检索任何键的名称

击键和字符转换

该系统包括几个特殊用途函数,用于转换各种击键消息提供的扫描代码、字符代码和虚拟键代码。 这些函数包括 MapVirtualKeyToAsciiToUnicodeVkKeyScan

此外,Microsoft Rich Edit 3.0 还支持 HexToUnicode IME,它允许用户使用热键在十六进制字符和 Unicode 字符之间进行转换。 这意味着,当 Microsoft Rich Edit 3.0 合并到应用程序中时,应用程序将继承 HexToUnicode IME 的功能。

热键支持

热键是一种组合键,用于生成 WM_HOTKEY 消息,即系统放置在线程消息队列顶部的消息,绕过队列中的任何现有消息。 应用程序使用热键从用户处获取高优先级键盘输入。 例如,通过定义由 CTRL+C 组合键组成的热键,应用程序可以允许用户取消冗长的操作。

为了定义热键,应用程序调用 RegisterHotKey 函数,指定生成 WM_HOTKEY 消息的键的组合、用于接收消息的窗口句柄以及热键的标识符。 当用户按下热键时,WM_HOTKEY 消息将放入创建窗口的线程的消息队列中。 消息的 wParam 参数包含热键的标识符。 应用程序可以为一个线程定义多个热键,但线程中的每个热键必须具有唯一标识符。 在应用程序终止之前,它应使用 UnregisterHotKey 函数销毁热键

应用程序可以使用热键控件,便于用户轻松选择热键。 热键控件通常用于定义激活窗口的热键;它们不使用 RegisterHotKeyUnregisterHotKey 函数。 相反,使用热键控件的应用程序通常会发送 WM_SETHOTKEY 消息来设置热键。 每当用户按下热键时,系统都会发送一条指定 SC_HOTKEY 的 WM_SYSCOMMAND 消息。 有关热键控件的详细信息,请参阅热键控件中的“使用热键控件”。

用于浏览和其他功能的键盘键

Windows 支持带有特殊键的键盘,用于浏览器功能、媒体功能、应用程序启动和电源管理。 WM_APPCOMMAND 支持额外的键盘键。 此外,还修改了 ShellProc 函数以支持额外的键盘键

组件应用程序中的子窗口不太可能能够直接实现这些额外键盘键的命令。 因此,按下其中一个键时,DefWindowProc 会向窗口发送 WM_APPCOMMAND 消息。 DefWindowProc 还会将 WM_APPCOMMAND 消息发送到其父窗口(以气泡形式)。 这类似于使用鼠标右键调用上下文菜单的方式,即 DefWindowProc 在右键单击时发送 WM_CONTEXTMENU 消息,并将其发送到其父项(以气泡形式)。 此外,如果 DefWindowProc 收到顶级窗口的 WM_APPCOMMAND 消息,它将调用代码为 HSHELL_APPCOMMAND 的 shell 挂钩

Windows 还支持 Microsoft IntelliMouse Explorer,它是一个有五个按钮的鼠标。 两个额外的按钮支持向前和向后浏览器导航。 有关详细信息,请参阅XBUTTON

模拟输入

若要模拟一系列不间断的用户输入事件,请使用 SendInput 函数。 函数接受三个参数。 第一个参数 cInputs 指示将模拟的输入事件数。 第二个参数 rgInputs 是一个 INPUT 结构数组,每个结构都描述一种输入事件类型和有关该事件的附加信息。 最后一个参数 cbSize 接受 INPUT 结构的大小(以字节为单位)

SendInput 函数的工作原理是将一系列模拟输入事件注入设备的输入流。 效果类似于重复调用 keybd_eventmouse_event 函数,只是系统会确保没有其他输入事件与模拟事件混为一体。 调用完成后,返回值指示成功播放的输入事件数。 如果此值为零,则表示输入被阻止。

SendInput 函数不会重置键盘的当前状态。 因此,如果用户在调用此函数时按下了任何键,它们可能会干扰此函数生成的事件。 如果担心可能存在干扰,请使用 GetAsyncKeyState 函数检查键盘的状态,并根据需要进行更正

语言、区域设置和键盘布局

语言是一种自然语言,例如英语、法语和日语。 子语言是在特定地理区域使用的自然语言的变体,例如英式英语和美式英语。 应用程序使用称为语言标识符的值来唯一标识语言和子语言。

应用程序通常使用区域设置来设置处理输入和输出所使用的语言。 例如,设置键盘的区域设置会影响键盘生成的字符值。 设置显示器或打印机的区域设置会影响显示或打印的字形。 应用程序通过加载和使用键盘布局来设置键盘的区域设置。 它们通过选择支持指定区域设置的字体来设置显示器或打印机的区域设置。

键盘布局不仅指定键在键盘上的物理位置,还确定通过按下这些键会生成的字符值。 每个布局标识当前输入语言,并确定由哪些键和键组合生成哪些字符值。

每个键盘布局都有一个相应的句柄,用于标识布局和语言。 句柄的低位字是语言标识符。 高位字是设备句柄,用于指定物理布局;或者为零,表示默认物理布局。 用户可以将任何输入语言与物理布局相关联。 例如,偶尔使用法语的英语用户可以将键盘的输入语言设置为法语,而无需更改键盘的物理布局。 这意味着用户可以使用熟悉的英语布局输入法语文本。

应用程序通常不会直接操作输入语言。 相反,用户设置语言和布局组合,然后在它们之间切换。 当用户单击使用其他语言标记的文本时,应用程序会调用 ActivateKeyboardLayout 函数来激活用户对该语言的默认布局。 如果用户使用不在活动列表中的语言编辑文本,应用程序可以使用该语言调用 LoadKeyboardLayout 函数以获得基于该语言的布局

ActivateKeyboardLayout 函数设置当前任务的输入语言。 hkl 参数可以是键盘布局的句柄或零扩展语言标识符。 可以通过 LoadKeyboardLayoutGetKeyboardLayoutList 函数获取键盘布局句柄。 也可使用 HKL_NEXT 和 HKL_PREV 值选择下一个或上一个键盘

GetKeyboardLayoutName 函数检索调用线程的活动键盘布局的名称。 如果应用程序使用 LoadKeyboardLayout 函数创建活动布局,GetKeyboardLayoutName 会检索用于创建布局的相同字符串。 否则,该字符串是对应于活动布局的区域设置的主要语言标识符。 这意味着函数不一定能区分具有相同主要语言的不同布局,因此无法返回有关输入语言的特定信息。 但是,GetKeyboardLayout 函数可用于确定输入语言

LoadKeyboardLayout 函数加载键盘布局并使该布局可供用户使用。 应用程序可以使用 KLF_ACTIVATE 值使当前线程的布局立即处于活动状态。 应用程序可以使用 KLF_REORDER 值对布局重新排序,而无需同时指定 KLF_ACTIVATE 值。 加载键盘布局时,应用程序应始终使用 KLF_SUBSTITUTE_OK 值,以确保选择用户的首选项(如果有)

对于多语言支持,LoadKeyboardLayout 函数提供了 KLF_REPLACELANG 和 KLF_NOTELLSHELL 标志。 KLF_REPLACELANG 标志指示函数在不更改语言的情况下替换现有的键盘布局。 尝试使用相同的语言标识符替换现有布局但不指定 KLF_REPLACELANG 是错误的。 KLF_NOTELLSHELL 标志可阻止函数在添加或替换键盘布局时通知 shell。 这对于在一系列连续调用中添加多个布局的应用程序很有用。 除最后一次调用外,所有调用都应使用此标志。

UnloadKeyboardLayout 函数受限,无法卸载系统默认输入语言。 这可确保用户始终有一种布局可用于输入文本,所用字符集与 shell 和文件系统使用的字符集相同。