虚拟键码和扫描码

来源:互联网 发布:arena仿真优化 编辑:程序博客网 时间:2024/05/18 03:21

虚拟键码 扫描码 ASCII的区别与联系

1.每个厂家的键盘上的每个键都对应一个扫描码。例如,对于键盘上的'ALT'键,其扫描码可能是4。

2.键盘的驱动程序会把扫描码转成虚拟码。例如将上面的扫描码4转化成虚拟码VK_MENU

3.由上面可以看出,扫描码通常是和具体的硬件(键盘)相关的,而虚拟码通常可以认为是硬件无关的。


二者可以通过如下函数联系起来:

MapVirtualKey。该函数可以在扫描码和虚拟码之间互相转换。

那么,如何通过程序获取键盘上各个键的状态呢?可以通过调用GetAsyncKeyState和GetKeyState函数来获取单个按键的状态,这两个函数的参数都是按键的虚拟码。也可以通过GetKeyboardState函数获取整个键盘上所有按键的状态。

那么,GetAsyncKeyState和GetKeyState既然都可以获取按键的状态,那么,二者有何区别呢?

GetAsyncKeyState获取的是按键的实时状态,而GetKeyState获取的是最近一个消息从线程的虚拟输入队列取走时该按键的状态,简单来说,就是窗口的消息循环在调用下一个GetMessage或PeekMessage前,使用该函数对特定按键的查询都返回的是同一个值,不管在调用下一个GetMessage或PeekMessage前用户是否按下了该键。

而调用GetAsyncKeyState函数则会返回该按键当前的状态。

要注意的是,调用GetAsyncKeyState/GetKeyState函数的线程必须有窗口,而且该窗口被激活,这样,函数调用才会有效。

在编写MFC应用程序过程中,需要对原有的CEdit作功能上的扩展,新生成的类CEditEx继承于CEdit,只允许用户输入数字和小数点。
要实现只允许用户输入数字和小数点,需要屏蔽非数字和小数点的字符,屏蔽工作在OnChar消息函数中进行:当按下键盘后,解发WM_CHAR消息,并进入OnChar消息函数,用::isdigit(nChar)验证数字,用nChar == '.'验证小数点,满足其一即调用父类OnChar函数继续处理:CEdit::OnChar(nChar, nRepcnt, nFlag)。

另外,一些按键如ESC、Tab键,当按下后并不能够触发WM_CHAR消息并进入OnChar消息函数,这时需要在PreTranslateMessage函数中用::TranslateMessage函数对pMsg消息翻译处理,其实也就是在这个函数中将虚拟键码(Virtua- Key) 即pMsg->wParam重新翻译为ASCII字符码,当翻译的ASCII字符码在0-127之间时,将向消息队列中递交字符消息WM_CHAR。
试一下:如果在调用::TranslateMessage函数后立刻用GetMessage截获消息MSG,将会发现下一个消息是WM_CHAR,并且wParam已被翻译为ASCII字符码。

理解1.
OnChar函数参数变量nChar是ASCII字符码,经过试验只有当按键所对应的ASCII码在0-127之间时才触发WM_CHAR消息,并进入OnChar消息函数,例如:键入上下左右键,是不会进入OnChar函数的。

理解2.
如果对计算机键盘I/O比较了解,应该知道键盘上每一个键对应一个扫描码,扫描码是由OEM生商制定的,不同厂商生产的键盘同样一个按键的扫描码都有可能出现不一致的情况,为摆脱由于系统设备不一致造成扫描码不一致的情形,通过键盘驱动程序将扫描码映射为统一的虚拟键码表示,如回车键定义为VK_RETURN,其16进制值为0x0D。

=========================================================================================================

微软积木○九七:虚拟键码


我们上一讲与大家讲过,我们的键盘其实送进来的键码为“扫描码”,那么什么是扫描码呢?就是我们键盘其实是好多好多的小开关,当我们按下键盘时,有些开关是开的,有些开关是关的,如果是开的,那么键盘就输入相应的键喽,如果是关,那么就不会输入这个字符了吧,说起来简单,那怎么实现呢?


  一个办法就是直接使用开关,然后产生一个八位的二进制,去让电脑停止运行,从而去响应我们的键盘传入的信息,大家看图:

  我们图中只给大家做了一个按键(黄色的啊,呵呵,像吗?),其实这里可以有256个按键,当然,真正是用不到这么多的按键的,那怎么办?用几三条线去表示功能键 Alt Ctrl Shift ,所以理论上我们只可以用这八个字节的键盘去设置64+3=67个按键。

  其实上面的仅仅是理论,我们的键盘当然不能这么设置了,我们键盘可以用十六位线啊,当然也可以用32位线或64位,随便啊,

  可惜的是问题来了,如果我们增加一倍的线,就会使得我们的键盘的接口增加一倍,现在都是USB 接口了,老式的接口其实就是有很多眼,好多针的,那种键盘,我们就称之为“传统键盘”吧。

  当然,技术是在飞快地发展,我们的键盘也不例外,我们的键盘现在使用了一种技术,称之为“扫描技术”。

  什么叫扫描技术呢?也就是说,某一个时刻,键盘中发送一个键码去测试某一个键,如果这个键按下了,那么这个键码就被置成1(这是牛刀的理论啊,事实上可能不一定是这样),如果这个码没有被按下,就被置成0,大家看下面的图:

  请大家看,如果我们测试图中的黄色的那个按键,那么我们就需要将上面的第六个开关关上,右边的第六个开关关上,那么也就是说,我们假设上面四根是控制上面十六个按键的话,那么上面就放上 0110 右边的也放上 0110 然后测试的结果送到进我们准备好的寄存器就一切OK了。

  当然,你可能说,那也只能测试一个按键的状态啊,是不?

  不错,可惜我们计算机测试的速度快啊,反肯定在你的手按下某个键来没来得及抬起,人家就可能已经若干次的全盘扫描就结束了,于是,就可以得到键盘上的按键信息,也正因为现代的键盘都是采用的这种扫描技术,所以其产生的码我们也称之为“扫描码”。

  与以前的键盘接口比起来,现代的扫描键盘的接口就要少得多,可能只有四五根脚就够了,甚至像现代的 USB 设备,就是四根脚,并且还有两根是送电的,呵呵。

  这说明什么呢?说明现代的 USB 技术,或者其他的一些技术,用的是数据流传送的方案,也就是说,一位一位地传送数据,而不是一次传送八位或者更多。

  这样做的好处是什么呢?我们可以很简单地改变一下驱动,就可以将一个十六位的程序变成32位的。当然理论上讲得通,但现实不会像我们想像得那么理想的。

  好了,那么从传统的机械键盘到扫描键盘,我们的扫描键盘其实完全有能力实现直接扫描出 ASCII 码,但还是为了兼容,我们的扫描码其实与机械键盘一样,其实应该属于一种键位码,举个例子吧:

  大家看我们最上面一排的键盘,键位分别为“QERTY...”是吧?那么他们的扫描码就分别为:

  Q 16

  W 17

  E 18

  R 19

  T 20

  Y 21

  事实就是如此,但是我们在 Windows 编程中,我们得不到这些代码,而我们能够得到的,那也只能是——虚拟代码。

  我们的 WM_KEYDOWN WM_KEYUP WM_SYSKEYDOWN WM_SYSKEYUP 中的 WPARAM 参数中,就包含这些虚拟代码,我们试着做一个程序让大家看看:
[cpp] view plaincopyprint?
  1.     int i;  
  2.   
  3.   char ch[20];  
  4.   
  5.   switch(uMsg)  
  6.   
  7.   {  
  8.   
  9.   case WM_KEYDOWN:  
  10.   
  11.   i=wParam;  
  12.   
  13.   sprintf(ch,"wParam=%d=>%c",i,i);  
  14.   
  15.   MessageBox(hwnd,ch,"扫描码测试",MB_OK);  
  16.   
  17.   break;  
  18.   
  19.   ……  

  请大家注意,虚拟代码没有扫描码那样晦涩难懂,但虚拟码就是虚拟码,是一键一码,例如,我们传统的键盘中,主键盘上的 1 与副键盘上的 1 虚拟码是不同的,而大写的 A 与小写的 a 虚拟码则是相同的,所以从这一点来说,我们我们虚拟码代码了键位。

  当然,好多的虚拟码都不是用 ASCII 表示的,所以其虚拟码都在 Windows 的头文件中定义了相应的宏,供我们使用,例如:
[cpp] view plaincopyprint?
  1.     #define VK_LBUTTON 0x01   
  2.   
  3.   #define VK_RBUTTON 0x02  
  4.   
  5.   #define VK_CANCEL 0x03  
  6.   
  7.   #define VK_MBUTTON 0x04  
  8.   
  9.   #define VK_BACK 0x08  
  10.   
  11.   #define VK_TAB 0x09  
  12.   
  13.   ……  
  是不是怪怪的,VK_LBUTTON 这些不是鼠标的按键吗?不错,可请大家放心的是,你按键盘上的键是绝对不会产生鼠标按键的消息码的,这个大家尽管放心好了。

  当然,第三个 VK_CANCEL 则表示同时按下了 Ctrl+Break 键,这个键在 DOS 年代相当重要,可能被称之为“冷启动”,而使用的 Ctrl+Alt+Del 则被称之为“热启动”。

  也就是说,不管谁编的 DOS 程序,只要使用 Ctrl+Break ,就可以重新启动这个程序,而不需要关闭电脑。

  当然,也会有其他一些虚拟代码喽:
[cpp] view plaincopyprint?
  1.     #define VK_BACK 0x08   
  2.   
  3.   #define VK_TAB 0x09  
  4.   
  5.   #define VK_CLEAR 0x0C  
  6.   
  7.   #define VK_RETURN 0x0D  
  8.   
  9.   #define VK_SHIFT 0x10  
  10.   
  11.   #define VK_CONTROL 0x11  
  12.   
  13.   #define VK_MENU 0x12  
  14.   
  15.   #define VK_PAUSE 0x13  
  16.   
  17.   #define VK_CAPITAL 0x14  
  18.   
  19.   ……  
  20.   
  21.   #define VK_ESCAPE 0x1B  
  22.   
  23.   ……  
  24.   
  25.   #define VK_SPACE 0x20  
  26.   
  27.   ……  
  当然,像 Page Up 啊,左箭头右箭头啊,像Insert啊,Delete啊等等等等,都有相应的虚拟键码。

  有一些虚拟键码是给系统用的,例如,我们按下 Prt Sc 键时,系统就会自动地将屏幕上的内容以图片的形式复制到剪贴板中,我们系列教材中的屏幕截图就是使用的这种技术截下来的,呵呵。

  当然,我们做程序也同样可以对这些虚拟键码进行编程,效果一样的。

  好玩的是,我们的功能键正常从 F1 到 F12 ,一般我们使用 F1 到 F10 但我们的定义中却有24个功能键虚拟码的定义,当然显然我们可以很容易地去扩充我们的键盘喽,可能以后或者以前的键盘上就会有二十四个功能键,呵呵。

  我们一般用不到那么多,大家知道就行了。

  上面我们说了,wParam 中定义了虚拟键码,那么LPARAM呢?

  重复计数

  这个计数估计现在用不到,呵呵,当我们的消息产生的按键消息比我们处理消息的速度快的话,那么系统会将这些消息组合成一个消息,并且将这个重复计数加一,就这样,用不着了。

  OEM扫描码

  什么是 OEM 扫描码?就是扫描码呗,呵呵,大家可以做以下程序看一看:
[cpp] view plaincopyprint?
  1.    ……  
  2.   
  3.   HDC hdc;  
  4.   
  5.   int i;  
  6.   
  7.   char ch[20];  
  8.   
  9.   switch(uMsg)  
  10.   
  11.   {  
  12.   
  13.   case WM_KEYDOWN:  
  14.   
  15.   hdc=GetDC(hwnd);  
  16.   
  17.   i=HIWORD(lParam) &0xff;  
  18.   
  19.   sprintf(ch,"扫描码为:%d",i);  
  20.   
  21.   TextOut(hdc,0,0,ch,15);  
  22.   
  23.   ReleaseDC(hwnd,hdc);  
  24.   
  25.   break;  
  26.   
  27.   ……  
  大家将上面的代码敲进电脑,然后顺序按键盘上按键,就可以看到我们的扫描码了,大家看“Q”是不是 16 ?W 则为 17 顺序来着的,呵呵。

  扩充标志

  这个标志表示我们的键盘是传统的键盘还是增强型键盘,其实说白了就是键盘的功能是不是扩充了,例如大家用新式的键盘,可能就会多一些按键,像我们的 Win 键等,都是后加的,甚至有些键盘上会有好多并不是传统键盘中的键,当然喽,那些键,除非人家专业的程序,咱也用不到,可我们要明白就是了,呵呵。

  Alt状态

  这个状态表明 Alt 键有没有被按下,如果按下,则向系统发送的是 WM_SYSKEYUP 和 WM_SYSKEYDOWN 消息,而没有被按下,则发送 WM_KEYUP WM_KEYDOWN 消息,仅此而已。

  所以如果大家响应 WM_KEYUP 消息,则不需要检测这个标志,肯定为 0,呵呵

  键的先前状态与转换状态

  大家有没有一直按住某个键的经历?如果一直按住,则按键会一直发送以下消息:

  WM_KEYDOWN

  WM_KEYDOWN

  ……

  WM_KEYDOWN

  WM_KEYUP

  也就是如果我们一直按键,则这个键就会持续地发 WM_KEYDOWN 消息,直到接收到 WM_KEYUP 结束(WM_SYSKEYDOWN 与 WM_SYSKEYUP 也一样)。

  那么转换状态,就是键有没有被按下,如果没按下则为0,即正在被释放时,为0,如果被按下,就为 1,也就是说,响应WM_KEYDOWN WM_SYSKEYDOWN 这个值就为 0,而如果我们响应 WM_KEYUP WM_SYSKEYUP 时,这个值就为 1

  然而,转换状态就不是这样,当响应 WM_KEYUP 时,转换状态为 0 但如果你一直按下键,则这个值就会不停地在0与1之间不断地变换,从而引起不断的 WM_KEYDOWN 的消息的产生。

  ·获得键盘状态

  像我们如果要获得键盘上的按键状态,怎么办?用下面的函数:

  GetKeySatate

  这个函数是去测试某个状态,例如我们用下面的函数:

  iState=GetKeyState(VK_SHIFT);

  大家看如下程序:
[cpp] view plaincopyprint?
  1.     ……  
  2.   
  3.   HDC hdc;  
  4.   
  5.   char ch[20];  
  6.   
  7.   SHORT iState;  
  8.   
  9.   switch(uMsg)  
  10.   
  11.   {  
  12.   
  13.   case WM_KEYDOWN:  
  14.   
  15.   hdc=GetDC(hwnd);  
  16.   
  17.   iState=GetKeyState(VK_SHIFT);  
  18.   
  19.   sprintf(ch,"Shift=%d",iState);  
  20.   
  21.   TextOut(hdc,0,0,ch,9);  
  22.   
  23.   ReleaseDC(hwnd,hdc);  
  24.   
  25.   break;  
  26.   
  27.   case WM_KEYUP:  
  28.   
  29.   hdc=GetDC(hwnd);  
  30.   
  31.   iState=GetKeyState(VK_SHIFT);  
  32.   
  33.   sprintf(ch,"Shift=%d",iState);  
  34.   
  35.   TextOut(hdc,0,0,ch,9);  
  36.   
  37.   ReleaseDC(hwnd,hdc);  
  38.   
  39.   break;  
  40.   
  41.   ……  
  当然,以上的函数 GetKeyState 只能在消息中响应按键消息的时候,去测试当时的状态,而这个函数却不会自主地去测试键盘的状态,例如大家如果想用 F2 功能键来暂停程序的执行,千万别用:

  while (GetKeyState (VK_F2) >= 0) ;

  会死机的,呵呵,程序会死在那儿,除非你在每次测试时,都保证你的消息队列中有了相应的按键消息,如果一旦是最后一个按键消息了,你的程序就进入了“死循环”,这可是真的死循环啊,呵呵。

  当然,你如果真的要知道按键的目前状态,你可以使用 GetAsyncKeyState 函数。

  有了以上的知识你可能很为你的成就感到高兴,例如你按下3键,如果此时检测到 Shift 键被按下,可能你觉得就可以处理成“#”字符了,可你千万不要这样做,因为假如你的用户使用的不是标准键盘,而键盘的第三个符号为“£”,那怎么办?

  这一讲的内容可能比较枯燥,但说实话,如果真的用 MFC 去做程序的话,大家一般是想不到要响应 WM_KEYDOWN 和 WM_KEYUP 消息的,并且即使响应了,那些消息中也会将一些必要的参数给传进来了,我们不可能讨论得这么细

  我们以后的 API 的章节,我们都会与大家详细地讨论每一个细节。

  假如我们不了解细节,我们就只能跟在别人的后面,别人的 MFC 中给了我们什么,我们就只能使用什么。

  说实话,我们讲的这些,其实就是那细细的细纱,我们的目的不是为了搭积木,我们的目的是为了“盖楼”,我们的“积木”也不是“积木”,而是盖楼用的砖瓦水泥。

  我们以前是教大家盖楼,而这几章是教大家如何“烧砖”,我们虽然走过不同的路,可是我们的目标却是共同的,我们的目标就是,能够真正地使用自己所学到的知识去做出一款款我们自己的软件。

  我们在这个道路上,当然还会有好多的路要走,希望大家跟着牛刀,一步步地走好,走踏实。
0 0
原创粉丝点击