应用
程序应该像接收鼠标输入一样可以接收键盘输入,Windows中的应用
程序是以窗体消息的形式来获取键盘输入。
本节包括以下内容: 键盘输入模型
键盘聚焦及激活
按键消息 字符消息 键状态
按键及字符转换
热键支持
浏览及其他功能键
模拟输入
语言、场所及键盘布局
键盘输入模型
系统通过安装当前键盘的设备
驱动来实现与应用
程序的设备无关性,也可以通过用户或应用
程序的键盘布局设置来实现语言无关性。键盘设备
驱动接收键盘的“扫描码”,然后把“扫描码”发送给键盘布局,通过键盘布局被转换为消息并发送到应用
程序的相应窗口。
键盘上每一个键都有一个唯一值,这个唯一值就称为“扫描码”(scan code),对于键盘上每个键来说,“扫描码”是设备相关的。当用户按键时会产生两次扫描码,一次是按下键时,一次是放开时。
然后,键盘
驱动把扫描码解释并转换(映射)为“虚键码”(virtual-key code),这个码是设备无关的,其值被系统所定义并用来标识每一个键。转换扫描码后,键盘布局会创建一个包含扫描码、虚键码以及其他按键信息的消息,并把这个消息放入系统消息队列。接着,系统从系统消息队列中删除该消息,再投递到相应线程的消息队列中。最后,线程的消息循环移除该消息并传递到相应窗口过程以进行处理。下图即键盘输入模型:
键盘聚焦及激活
系统投递键盘消息到前台线程的消息队列中,这个前台线程应该是创建当前获得焦点的窗口的线程。键盘聚焦(Keyboard focus)是一个窗体的临时属性。系统通过键盘聚焦来向所有的显示窗体共享键盘,从用户的角度讲,键盘聚焦也就意味着,从一个窗口转到另外一个。获取焦点的窗口接收(从创建它的线程的消息队列中)接收所有的键盘消息,直到焦点转移到另外的窗体上。
线程可以通过调用GetFocus函数来确定那个窗口为当前窗口(已经键盘聚焦),也可以通过SetFocus来使哪个窗口获取焦点。当键盘聚焦从一个窗口换到另外一个时,系统会发送WM_KILLFOCUS到失去焦点的窗口,然后发送WM_SETFOCUS消息到获得焦点的窗口。
键盘聚焦与活动窗口有一定关系,活动窗口(active window)是最上层用户正在操作的窗口。键盘聚焦的窗体或者是活动窗体,或者是活动窗体的子窗体。为了帮助用户辨别活动窗体,系统把它置于Z-order的最上层,并高亮它的标题栏及边框。
用户可以通过点击来激活一个*窗体,也可以用ALT+TAB(ALT+ESC)组合键或者通过任务列表来选择一个窗体。线程可以通过SetActiveWindow函数来激活一个*窗体,也可以通过GetActiveWindow函数来确定它创建的*窗体是否已被激活。
当一个窗体的活动状态变更时,系统会发送WM_ACTIVATE消息。wParam参数的低字(译者注:wParam在Win32中是一个32位的整数,从高到低依次编码的话应该是31、30、29、……、2、1、0,低字指的是15到0)部分 如果为0表示窗体未激活,否则表示激活。默认的窗口处理过程收到WM_ACTIVATE消息时,会设置键盘聚焦到活动窗口。
要阻止应用
程序接收键盘及鼠标事件的话,可以使用BlockInput。需要注意的是,BlockInput函数不会影响异步的键盘输入状态表,也就是说,当输入被阻塞时,调用SendInput函数会改变异步键盘输入状态表。(译者注:原文直译可能让人更加摸不着北,应该是说,BlockInput函数不会影响异步的键盘输入,这个时候,调用SendInput的话,还是会改变异步的键盘输入状态信息)。
按键消息
按下键会产生WM_KEYDOWN或WM_SYSKEYDOWN消息,然后会被放置在当前键盘聚焦的窗口所在线程的消息队列中。同样释放按键也会产生消息,这个消息将会是WM_KEYUP或者WM_SYSKEYUP。
Key-up与Key-down消息通常应该成对出现,但如果用户按下键后呆足够长时间的话,键盘会自动重复描述这一情况,系统会对应产生一系列的WM_KEYDOWN或WM_SYSKEYDOWN事件,但不管怎样,用户释放按键时,只会产生一个WM_KEYUP或WM_SYSKEYUP消息。
本节包括以下内容: 系统及非系统按键
虚拟码描述
按键消息标志
系统及非系统按键
系统中系统按键与非系统按键是截然不同的,系统按键产生系统按键消息:WM_SYSKEYDOWN、WM_SYSKEYUP,而非系统按键产生非系统按键消息:WM_KEYDOWN与WM_KEYUP。
如果你的窗口处理过程确实有必要处理系统按键消息的话,一定要确认在处理完毕后,该过程把消息传递给了DefWindowProc函数。否则,所有的系统操作,包括ALT键都会失效,即便窗口的确获得了焦点。也就是说,用户将不能访问窗口菜单或者系统菜单,又或者使用ALT+ESC(ALT+TAB)组合键激活其他窗口了。
系统按键消息主要是系统使用的,系统用这些消息提供菜单的内置键盘接口,以及允许用户控制激活不同的窗口。系统按键消息通常是用户按下ALT及某个键的组合键时产生的,又或者在用户按下但没有窗体拥有键盘焦点(比如,激活的应用
程序最小化)时产生。如果消息产生的话,就会发送到激活窗体的消息队列中。
非系统按键消息是需要应用
程序窗体处理的,DefWindowProc函数不会对这些消息作任何处理,窗体的处理过程可以忽略任意的不需要的非系统按键消息。
虚键码描述
按键消息的wParam参数包含了按键的虚键码,窗口处理过程根据这个虚键码来处理或者忽略一个按键消息。
典型的窗口处理过程中仅会处理一小部分按键消息,其余的部分只是简单的接收并忽略。例如,窗口处理过程可能仅处理WM_KEYDOWN消息,以及光标移动键、换档键(也可以说控制键),还有功能键的虚键码。窗口处理过程中一般不会处理字符键的按键消息,相反,应该使用TranslateMessage函数把它们转换成字符消息。关于TranslateMessage与字符消息的更多信息,请参见字符消息(Character Message)。
按键消息标志
按键消息的lParam消息中包含了按键的额外信息,其中包括:重复次数、扫描码、扩充键标志、上下文标志、前键状态标志,以及转换状态标志。下图指示了这些标志及值在lParam中的位置:
按键标志中可以存储以下值: KF_ALTDOWN ALT键标志,标识ALT键是否按下。
KF_DLGMODE 对话框标志,标识对话框是否激活。
KF_EXTENDED 扩充键标志
KF_MENUMODE 菜单模式标志,标识菜单是否激活。
KF_REPEAT 重复次数
KF_UP 转换状态标志
重复次数
你可以通过检查重复次数,来确定一次按键是否产生了多个按键消息。如果键盘产生WM_KEYDOWN或WM_SYSKEYDOWN消息后,超过一定时间应用
程序还未处理这些消息的话,系统就会增加重复计数。通常,是因为用户保持按键状态较长时间,而启动了键盘的自动重复技术机制。系统不会因此产生多个键盘消息,相反,系统会组合这些消息,并增加这个消息的重复次数。释放一个按键时不会启动自动重复机制,所以WM_KEYUP与WM_SYSKEYUP消息的重复次数总会是1。
扫描码
扫描码是用户按键时由键盘硬件产生的,这个值是设备相关的,用来标识不同的键,对于字符也是通过按键来表示的。应用
程序通常会忽略扫描码,实际上,它使用设备无关的虚键码来说明按键消息。
扩充键标志
扩充键标志用来标识按键消息中是否包含了增强型键盘的附加键,这些扩充键包括:键盘右手边的ALT、CTRL键,INS、DEL、HOME、END、PAGE UP、PAGE DOWN,小键盘左边的方向键,NUM LOCK、BREAK(CTRL+PAUSE)、PRINT SCRNT以及小键盘上的除号(/)键及ENTER键。如果键为以上键的话,扩充键标志即会设置。
上下文标志
上下文标志是为了说明按键消息产生时,ALT键是否已经按下,如果为1,表示ALT键已经按下,否则没有按下。
前键状态标志
前键状态标志用来说明产生按键消息的键原来是抬起的还是按下的。如果为1,表示原来是按下的,0原来是抬起的。你可以通过该标志来辨别该消息是否是由键盘自动重复机制产生的。如果为1,表示WM_KEYDOWN与WM_SYSKEYDOWN消息是自动产生的,对于WM_KEYUP与WM_SYSKEYUP消息来说,该标志总会为0。
转换状态标志
转换状态标志用来说明该消息是按下键时还是释放键时产生的,对于WM_KEYDOWN、WM_SYSKEYDOWN来说该标志总会为0,对于WM_KEYUP、WM_SYSKEYUP总会是1。
字符消息
按键消息可以提供许多按键的基本信息,但却不提供字符键的字符码,要想得到字符码,应用
程序必须在自己的线程循环中包含TranslateMessage函数,TranslateMessage传递WM_KEYDOWN或WM_SYSKEYDOWN消息到键盘布局,通过检查消息的虚键码,如果发现它是一个字符键的话,键盘布局就会提供一个字符码的等价物(会考虑SHIFT及CAPS LOCK键的状态),然后产生一个包括字符码的字符消息,并放到消息队列的头部。消息循环的下一次处理就会把字符消息从队列中删除,并分发给相应的窗口处理过程。
本节包含以下内容: 非系统字符消息
不使用字符的消息
非系统字符消息
在窗口的处理过程中可以处理如下的字符消息:WM_CHAR、WM_DEADCHAR、WM_SYSCHAR、WM_SYSDEADCHAR以及WM_UNICHAR。TranslateMessage处理WM_KEYDOWN消息时会产生WM_CHAR或WM_DEADCHAR消息,与之类似,当处理WM_SYSKEYDOWN消息时会产生WM_SYSCHAR或WM_SYSDEADCHAR消息。
应用
程序处理键盘输入时通常会忽略除WM_CHAR与WM_UNICHAR外的所有消息,只是把它们传递给DefWindowProc函数。注意:WM_CHAR使用了16位Unicode转换格式(UTF),WM_UNICHAR使用了UTF-32格式。系统使用WM_SYSCHAR及WM_SYSDEADCHAR消息实现了菜单助记符的工程。
所有字符消息中的wParam参数包含了字符键的字符码,其值取决于接收消息的窗口的窗口类,如果是用的RegisterClass函数的Unicode版本注册的窗口类,系统就会向所有那个类的窗口实例提供Unicode字符,否则就是ASCII字符码,更多信息,请参照Unicode及字符集。
字符消息中lParam参数值与key-down消息中lParam参数值相同。更多信息,参照按键消息标志。
不使用字符的消息
有些非English键盘中,包含一些本身不产生字符的字符键,它们只是用来为后续的按键提供一个区分(译者注:或者应该说是,为了区分后续的按键吧)。这些键就被称为无用键(dead keys),These keys are called dead keys. 德文键盘中的扬声符就是一个无用键的例子,为了输入由“o”及扬声符组成的符号(译者注:应该是类似“ó”的符号),德文的用户需要按下扬声符键,然后是“o”键。获得焦点的窗口就会收到以下消息序列: WM_KEYDOWN
WM_DEADCHAR
WM_KEYUP
WM_KEYDOWN
WM_CHAR WM_KEYUP
当TranslateMessage处理无用键的WM_KEYDOWN消息时,就会产生WM_DEADCHAR消息,尽管WM_DEADCHAR消息的wParam参数中包含了扬声符这个无用键的字符码,但应用
程序通常会忽略这个消息,而会接着处理后续按键的WM_CHAR消息。WM_CHAR消息的WM_CHAR参数中包含了那个字母与扬声符的字符码。如果后续的按键产生的字符不能与扬声符组合,系统就会产生两个WM_CHAR消息,第一个消息的wParam参数中包含了扬声符的字符码,第二个消息的wParam参数中包含了后续字符键的字符码。
当TranslateMessage处理一个系统无用键(与ALT的组合键)的WM_SYSKEYDOWN消息时,就会产生WM_SYSDEADCHAR消息。应用
程序通常忽略WM_SYSDEADCHAR消息。
键状态
处理键盘消息时,应用
程序除了需要处理当前按键消息的那个按键外,还可能需要确定另外一个键的状态。比如,一个字处理软件,可能允许用户使用SHIFT+END来选择一个文本块,那这个应用
程序就必须在任意收到END键的按键消息时,检验SHIFT键是否已按下。应用
程序可以处理当前的按键消息时使用GetKeyState函数来确定一个虚键的状态,也可以通过GetAsyncKeyState函数来获得当前某个虚键的状态。
键盘布局维护着一个按键名称列表,仅产生一个字符的按键的名称与按键的名称相同,非字符键如TAB、ENTER的名称以字符串的形式存储。应用
程序可以通过调用GetKeyNameText函数来从设备
驱动中得到任意键的名称。
按键及字符转换
系统包含若干特殊用途的函数来转换扫描码、字符码、以及虚键码,这些函数包括:MapVirtualKey,ToAscii,ToUnicode及VkKeyScan。
另外,Microsoft? Rich Edit 3.0支持HexToUnicode IME,它可以让用户通过热键在十六进制及Unicode字符之间转换,这也意味着Rich Edit 3.0可以合并到一个应用
程序中,使得这个应用
程序可以继承HexToUnicode IME的功能。
热键支持
一个热键是一个可以产生WM_HOTKEY消息的键组合,系统会把这个消息放置到消息队列的顶部,而绕开任何队列中已有的消息。应用
程序使用热键可以向用户提供更高优先级的键盘输入,例如,通过定义CTRL+C组合键,应用可以使用户避免冗长的操作。
要定义热键的话,应用
程序需要调用RegisterHotKey函数来指定一个可以产生WM_HOTKEY的组合键,再传入接收消息的窗体的handle以及热键的唯一标识就可以了。用户按下热键时,创建那个窗体的线程的消息队列中就会收到WM_HOTKEY。消息中的wParam参数包含热键的标识。应用
程序可以在一个线程中定义多个热键,但每个热键必须有一个唯一标识。应用
程序终止前,应该调用UnregisterHotKey函数来撤销热键。
应用
程序可以使用一个热键控件,使得用户能够方便自定义热键,热键控件通常用来定义一个热键,使得能够激活一个窗口,他们不使用RegisterHotKey及UnregisterHotKey函数,相反,使用热键控件的应用
程序通常发送WM_SETHOTKEY消息来设置热键,无论何时,用户按下热键,系统会发送一个指定SC_HOTKEY的WM_SYSCOMMAND消息。详细信息,可参照“应用热键控件”。
浏览及其他功能键
Microsoft Windows?可以支持某些特殊键:浏览器功能键、媒体功能键、应用
程序载入键以及电源管理键。WM_APPCOMMAND可以提供这些特殊键的支持。另外,ShellProc也被修改为可以支持额外键的函数了。
在一个组件应用
程序中的一个子窗体直接执行这些额外键的命令是不太可能的,因此,一旦有这样的键按下的话,DefWindowProc将发送一个WM_APPCOMMAND消息到一个窗体,DefWindowProc也会(冒泡式的)引发(bubble)父窗体处理WM_APPCOMMAND消息。 这同点击鼠标右键弹出上下文菜单的模式类似,DefWindowProc在鼠标右击时发送一个WM_CONTEXTMENU消息,冒泡式的到它的父亲。需要额外说明的是,如果DefWindowProc收到一个给*窗体的WM_APPCOMMAND消息的话,就会以HSHELL_APPCOMMAND代码调用一个外壳钩子(shell hook)。
Windows也支持Microsoft IntelliMouse? Explorer,它是一个有五个按键的鼠标。两个额外键支持浏览器的前进后退。更多信息,请参照XBUTTON。
模拟输入
要模拟一个连续的一系列的用户输入,就可以使用SendInput函数。这个函数需要三个参数:第一个参数cInputs,表示将要模拟的输入事件的个数(INPUT数组的大小);第二个参数rgInputs,是INPUT结构的数组,每个元素都描述了输入事件类型及事件的附加信息;最后一个是cbSize,是按字节计的INPUT结构的大小。
SendInput函数通过向设备输入流中注入一系列的模拟事件来实现模拟输入,效果类似于重复调用keybd_event或mouse_event函数,除了系统需要确认模拟事件件没有插入其他输入事件。一旦调用结束,其返回值就会指出有几个输入事件成功运行了,如果为0,则说明输入被阻塞了。
SendInput函数不会重设当前的键盘状态,因此,如果调用此函数时,用户已经按下了什么键的话,就可能被函数产生的事件所干扰,如果你担心潜在的冲突的话,可以用GetAsyncKeyState函数检查键盘状态,并按需要纠正。
语言、场所及键盘布局
语言指的是自然语言,如English、French及Japanese,子语言是某个特定地理区域的自然语言的变种,如英语的子语言就包括England语及美国英语。而应用
程序所指的是语言标识,用来唯一的辨别语言及子语言。
应用
程序通常使用场所(locales)来设置指定输入输出的语言,例如设置键盘的场所会影响键盘产生的字符值;设置显示器或打印机的场所会影响字形显示或打印。应用
程序通过调入使用键盘布局来设置场所,通过选择指定场所所支持的字体来设置显示器或打印机的场所。
键盘布局不仅是用来自定按键的物理位置,而且也是用来确定那些按键所决定的字符值的。每个布局表示当前的输入语言,也用来确定哪个或哪些键组合可以产生哪些字符值。
每种键盘布局都有相应的标识布局及语言的句柄(handle),句柄的低字部分是语言标识符,高字部分是设备句柄(描述了物理布局),或者为0,表示使用默认的物理布局。用户可以用任意的输入语言联合到一个物理布局上。如,说English的用户,可能不时地要去French工作,他就可以设置输入语言为French,而不用变更键盘物理布局,这也意味着用户可以用自己熟悉的English布局输入French文字。
应用
程序不要想直接操作输入语言,相反,用户可以设置语言与布局的组合,然后在他们之间交替变更。当用户点击进入一个不同语言的文本中时,应用
程序
调用ActivateKeyboardLayout函数来激活用户的默认布局;如果用户需要编辑一段文本,但它的语言不再现在的列表中,应用
程序将调用LoadKeyboardLayout函数得到一个基于那个语言的布局。
ActiveKeyboardLayout函数为当前任务设置输入语言,hkl参数可以是键盘布局的句柄或者0扩充的语言标识,键盘布局句柄可以通过LoadKeyboardLayout或GetKeyboardLayoutList函数获得,HKL_NEXT及HKL_PREV也可以用来选择下一个或者上一个键盘。
GetKeyboardLayoutName函数可以获取调用线程的当前键盘布局的名称。如果应用
程序用LoadKeyboardLayout函数创建了当前布局,GetKeyboardLayoutName将得到创建布局时使用的相同的字符串,否则,将得到当前布局的相应现场的主要语言标识符,这也意味着该函数可能不会区分相同主要语言的不同布局,因此不能返回输入语言的特定信息。然而GetKeyboardLayout函数可以确定使用的输入语言。
LoadKeyboardLayout函数调入一个键盘布局并使它对用户可用。应用
程序可以通过使用KLF_ACTIVATE使得布局立即对当前线程可用,如果没有指定KLF_ACTIVATE也可以使用KLF_REORDER重新设置布局。应用
程序应该在调入键盘布局时使用KLF_SUBSTITUTE_OK,以便确保用户的优先设置,如果有,就会被选择。
要多语言支持的话,LoadKeyboardLayout提供KLF_REPLACELANG与KLF_NOTELLSHELL标志。KLF_REPLACELANG标志可以不用改变语言而直接替换为一个存在的键盘布局。尝试替换为一个相同语言标识的已存在的布局,不指定KLF_REPLACELANG是错误的。KLF_NOTELLSHELL标志会组织函数在布局添加或替换时通知shell。 在连续的一系列调用中,这很有用,除了最后一次调用外,其他调用都应该使用该标志。
UnloadKeyboardLayout函数是受限的,它不能调出系统的默认输入语言,这可以保证用户总是同shell及温文件系统一样输入相同字符集的文字。