关于键盘输入

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

本部分涵盖了以下主题:

键盘输入模型

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

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

键盘设备驱动程序解释扫描代码,并将 (映射转换) 为 虚拟键代码,系统定义的与设备无关的值用于标识密钥的用途。 转换扫描代码后,键盘布局会创建一条消息,其中包含扫描代码、虚拟键代码以及有关击键的其他信息,然后将该消息放入系统消息队列中。 系统从系统消息队列中删除该消息,并将其发布到适当线程的消息队列中。 最终,线程的消息循环将删除该消息,并将其传递到适当的窗口过程进行处理。 下图说明了键盘输入模型。

键盘输入处理模型

键盘焦点和激活

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

如果任何) 当前具有键盘焦点,则线程可以调用 GetFocus 函数来确定其哪个 windows (。 线程可以通过调用 SetFocus 函数将键盘焦点放到它的窗口之一。 当键盘焦点从一个窗口改变到另一个窗口时,系统会将一条 wm _ KILLFOCUS 消息发送到已失去焦点的窗口,然后将 wm _ SETFOCUS 消息发送到已获得焦点的窗口。

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

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

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

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

击键消息

按某个键会导致将 wm _ KEYDOWNwm _ SYSKEYDOWN 消息放置在附加到具有键盘焦点的窗口的线程消息队列中。 释放密钥会导致将 wm _ KEYUPwm _ SYSKEYUP 消息放入队列中。

关键消息和键关闭消息通常成对出现,但是如果用户按住某个键的时间足以启动键盘的自动重复功能,则系统会在一行中生成大量的 wm _ KEYDOWNwm _ SYSKEYDOWN 消息。 然后,当用户释放密钥时,它将生成单个 WM _ KEYUPWM _ SYSKEYUP 消息。

本部分涵盖了以下主题:

系统和非系统击键

系统在系统击键和非系统击键之间进行区分。 系统击键会生成系统击键消息 _ SYSKEYDOWNwm _ SYSKEYUP。 非系统击键会产生非系统击键消息: WM _ KEYDOWNwm _ KEYUP

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

系统击键消息主要供系统使用,而不是由应用程序使用。 系统使用它们向菜单提供内置键盘接口,并允许用户控制活动的窗口。 当用户键入键并将其与 ALT 键结合使用时,或当用户的类型和无窗口具有键盘焦点时,将生成系统击键消息 (例如,当活动应用程序被最小化时) 。 在这种情况下,消息将发送到附加到活动窗口的消息队列。

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

描述的 Virtual-Key 代码

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

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

击键消息标志

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

击键消息的 lparam 参数中的标志和值的位置

应用程序可以使用以下值来操作击键标志。

说明
KF _ ALTDOWN 操作 ALT 键标志,该标志指示是否按下 ALT 键。
KF _ DLGMODE 操作对话框模式标志,该标志指示对话框是否处于活动状态。
KF _ EXTENDED 操作扩展键标志。
KF _ MENUMODE 操作菜单模式标志,该标志指示菜单是否处于活动状态。
KF _ REPEAT 操作上一个键状态标志。
KF _ UP 操作转换状态标志。

示例代码:

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

    BYTE scanCode = LOBYTE(HIWORD(lParam));                             // scan code
    BOOL scanCodeE0 = (HIWORD(lParam) & KF_EXTENDED) == KF_EXTENDED;    // extended-key flag, 1 if scancode has 0xE0 prefix

    BOOL upFlag = (HIWORD(lParam) & KF_UP) == KF_UP;                    // transition-state flag, 1 on keyup
    BOOL repeatFlag = (HIWORD(lParam) & 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 altDownFlag = (HIWORD(lParam) & KF_ALTDOWN) == KF_ALTDOWN;     // ALT key was pressed

    BOOL dlgModeFlag = (HIWORD(lParam) & KF_DLGMODE) == KF_DLGMODE;     // dialog box is active
    BOOL menuModeFlag = (HIWORD(lParam) & KF_MENUMODE) == KF_MENUMODE;  // menu is active
    
    // ...
}
break;

重复计数

可以检查重复计数,以确定击键消息是否表示多次击键。 当键盘生成 WM _ KEYDOWNWM _ SYSKEYDOWN 消息的速度比应用程序可以处理的消息快时,系统会递增计数。 当用户持有足够长的密钥以启动键盘的自动重复功能时,通常会发生这种情况。 系统将消息合并为单个向下键消息,并递增重复计数,而不是使用生成的向下键消息填充系统消息队列。 释放密钥无法启动自动重复功能,因此 WM _ KEYUPWM _ SYSKEYUP 消息的重复计数始终设置为 1。

扫描代码

扫描代码是键盘硬件在用户按下键时生成的值。 它是一个与设备相关的值,用于标识按下的键,而不是键表示的字符。 应用程序通常忽略扫描代码。 相反,它使用与设备无关的虚拟密钥代码来解释击键消息。

Extended-Key 标志

扩展键标志指示击键消息是否源自增强键盘上的附加键之一。 扩展键由键盘右侧 Alt 和 CTRL 键组成;数字键盘左侧群集中的 INS、DEL、HOME、END、PAGE UP、PAGE DOWN 和箭头键;NUM LOCK 键;CTRL+PAUSE (BREAK) 键;PRINT SCRN 键;和数字键盘 (/) 和 ENTER 键的除号。 如果密钥是扩展键,则设置扩展键标志。

如果指定,扫描代码前面有一个前缀字节,其值为 224 0xE0 (224) 。

上下文代码

上下文代码指示在生成击键消息时 ALT 键是否关闭。 如果 ALT 键关闭,则代码为 1;如果已启动,则代码为 0。

上一Key-State标志

前面的键状态标志指示生成击键消息的键以前是向上还是向下。 如果密钥以前关闭,则为 1;如果密钥以前已启动,则为 0。 可以使用此标志标识由键盘自动重复功能生成的击键消息。 对于自动重复功能生成的 WM _ KEYDOWNWM _ SYSKEYDOWN 击键消息,此标志设置为 1。 对于 WM _ KEYUPWM _ SYSKEYUP 消息,它始终设置为 1。

Transition-State 标志

转换状态标志指示是按键还是释放键生成击键消息。 对于 WM _ KEYDOWNWM _ SYSKEYDOWN 消息,此标志始终设置为 0;对于 WM _ KEYUPWM _ SYSKEYUP 消息,此标志始终设置为 1。

字符消息

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

本部分涵盖了以下主题:

非系统字符消息

窗口过程可以接收以下字符消息 :WM _ CHAR、WM _ DEADCHAR、WM _ SYSCHAR、WM _ SYSDEADCHARWM _ UNICHARTranslateMessage函数在处理 WM _ KEYDOWN 消息时生成 WM CHAR 或 WM _ _ DEADCHAR消息。 同样,它会在处理 WM SYSKEYDOWN 消息时生成 WM _ SYSCHARWM _ SYSDEADCHAR 消息。 _

处理键盘输入的应用程序通常忽略 除 WM _ CHAR 和 WM _ UNICHAR 消息外的所有消息,将任何其他消息传递给 DefWindowProc 函数。 请注意 ,WM _ CHAR 使用 UTF (16 位 Unicode 转换格式) WM _ UNICHAR 使用 UTF-32。 系统使用 WM _ SYSCHARWM _ SYSDEADCHAR 消息来实现菜单助记键。

所有字符消息的 wParam 参数都包含按下的字符键的字符代码。 字符代码的值取决于接收消息的窗口的窗口类。 如果使用了 RegisterClass 函数的 Unicode 版本来注册窗口类,则系统会向该类的所有窗口提供 Unicode 字符。 否则,系统将提供 ASCII 字符代码。 有关详细信息,请参阅 Unicode 和字符集

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

Dead-Character消息

某些非英语键盘包含不应自行生成字符的字符键。 相反,它们用于向后续击键生成的字符添加音调符号。 这些密钥称为 死键。 德语键盘上的圆周键是死键的一个示例。 若要输入由具有周数的"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函数在处理来自系统死密钥的 WM _ SYSKEYDOWN消息时生成 WM _ SYSDEADCHAR消息 (该死键与 ALT 键一起按下) 。 应用程序通常忽略 WM _ SYSDEADCHAR 消息。

密钥状态

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

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

击键和字符翻译

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

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

Hot-Key支持

热键 是生成 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 指示将模拟的输入事件数。 第二个参数 rgInputsINPUT 结构的数组,每个结构描述一种类型的输入事件和有关该事件的其他信息。 最后一个参数 cbSize 接受 INPUT 结构的大小(以字节为单位)。

SendInput函数的工作原理是,将一系列模拟输入事件注入设备的输入流。 效果类似于重复调用 keybd _ 事件 或鼠标 事件 _ 函数,只不过系统可确保其他输入事件不会与模拟事件一起终止。 调用完成后,返回值指示已成功播放的输入事件数。 如果此值为零,则输入被阻止。

SendInput函数不会重置键盘的当前状态。 因此,如果用户在调用此函数时按下了任何键,则可能会干扰此函数生成的事件。 如果担心可能的干扰,请通过 GetAsyncKeyState 函数检查键盘状态,并在必要时更正。

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

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

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

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

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

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

ActivateKeyboardLayout函数设置当前任务的输入语言。 hkl 参数可以是键盘布局的句柄或零扩展的语言标识符。 可以从 LoadKeyboardLayoutGetKeyboardLayoutList 函数获取键盘布局句柄。 HKL _ NEXTHKL _ PREV 值还可用于选择下一个或上一个键盘。

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

LoadKeyboardLayout函数加载键盘布局,并允许用户使用布局。 应用程序可以使用 KLF _ ACTIVATE 值立即激活当前线程的布局。 应用程序可以使用 KLF _ REORDER 值对布局重新排序,而无需同时指定 KLF _ ACTIVATE 值。 加载键盘布局时,应用程序应始终使用 KLF _ SUBSTITUTE _ OK 值,以确保选择用户的首选项(如果有)。

对于多语言支持 ,LoadKeyboardLayout 函数提供 KLF _ REPLACELANGKLF _ NOTELLSHELL 标志。 KLF _ REPLACELANG 标志指示函数替换现有键盘布局,而无需更改语言。 尝试使用同一语言标识符替换现有布局,但不指定 KLF _ REPLACELANG 是一个错误。 KLF _ NOTELLSHELL 标志可防止函数在添加或替换键盘布局时通知 shell。 这适用于在连续的一系列调用中添加多个布局的应用程序。 除了最后一个调用外,应在所有 中都使用此标志。

UnloadKeyboardLayout函数受到限制,因为它无法卸载系统默认输入语言。 这可确保用户始终具有一个布局,可以使用 shell 和文件系统使用的相同字符集输入文本。