博客园  :: 首页  :: 联系 :: 管理

关于键盘输入

Posted on 2007-03-23 12:37  sunrack  阅读(1494)  评论(0编辑  收藏  举报
应用程序应该像接收鼠标输入一样可以接收键盘输入,Windows中的应用程序是以窗体消息的形式来获取键盘输入。

  本节包括以下内容:

键盘输入模型

  系统通过安装当前键盘的设备驱动来实现与应用程序的设备无关性,也可以通过用户或应用程序的键盘布局设置来实现语言无关性。键盘设备驱动接收键盘的“扫描码”,然后把“扫描码”发送给键盘布局,通过键盘布局被转换为消息并发送到应用程序的相应窗口。

  键盘上每一个键都有一个唯一值,这个唯一值就称为“扫描码”(scan code),对于键盘上每个键来说,“扫描码”是设备相关的。当用户按键时会产生两次扫描码,一次是按下键时,一次是放开时。

  然后,键盘驱动把扫描码解释并转换(映射)为“虚键码”(virtual-key code),这个码是设备无关的,其值被系统所定义并用来标识每一个键。转换扫描码后,键盘布局会创建一个包含扫描码、虚键码以及其他按键信息的消息,并把这个消息放入系统消息队列。接着,系统从系统消息队列中删除该消息,再投递到相应线程的消息队列中。最后,线程的消息循环移除该消息并传递到相应窗口过程以进行处理。下图即键盘输入模型:

Keyboard input processing model

键盘聚焦及激活

  系统投递键盘消息到前台线程的消息队列中,这个前台线程应该是创建当前获得焦点的窗口的线程。键盘聚焦(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_CHARWM_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”键。获得焦点的窗口就会收到以下消息序列:

  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消息的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及温文件系统一样输入相同字符集的文字。



原文:ms-help://MS.MSDNQTR.2003FEB.2052/winui/winui/windowsuserinterface/userinput/keyboardinput/aboutkeyboardinput.htm

© 2003 Microsoft Corporation. All rights reserved.

翻译:Room3rd@hotmail.com,2004-11-22止