鼠标消息与键盘消息
2015-12-24 22:12
190 查看
1,虚拟键(VK_*)
键盘上每一个键对应一个扫描码,扫描码是OEM厂商制定的,不同的厂商生产的键盘同样一个按键的扫描码都有可能出现不一致的情况,为了摆脱由于系统设备不一致的情况,通过键盘驱动程序将扫描码映射为统一的虚拟键码表示,从而达到所有的设备都有一个统一的虚拟键,比如回车键的虚拟键是VK_RETURN。
Windows定义的虚拟键都定义在WinUser.h这个头文件里面,都是以VK_作为前缀。
2,激活/关闭窗口对键盘的消息
激活/关闭消息:WM_SETFOCUS/WM_KILLFOCUS
创建光标:CreateCaret(...)
设置光标位置:SetCaretPos(…)
在窗口中显示光标:ShowCaret(…)
销毁光标:DestroyCaret()
3,键盘消息
1)字符消息
系统字符消息
WM_SYSCHAR:系统字符
WM_SYSDEADCHAR:系统死字符
非系统按键消息
WM_CHAR:非系统字符
WM_DEADCHAR:非系统死字符
2)按键消息
系统按键消息:与ALT键相组合的组合键(无论用户处理否,都需要最后调用DefWindowProc(hWnd,iMessage,wParam,lParam))
WM_SYSKEYDOWN
WM_SYSKEYUP
非系统按键消息:
WM_KEYDOWN
WM_KEYUP
注意:
a) 除Print键之外都有“按下”消息。
b) 所有键都存在“弹起”消息。
c) 根据MSDN说明,只有下面这些键才会产生字符消息:
任何字符键
回退键(BACKSPACE)
回车键(carriage return)
ESC
SHIFT + ENTER (linefeed 换行)
TAB
我们是怎么收到WM_CHAR的呢?就是因为我们在消息循环时调用了TranslateMessage对键盘消息进行翻译,
如果消息为WM_KEYDOWN或者WM_SYSKEYDOWN,并且按键与位移状态相组合产生一个字符,则TranslateMessage把字符消息放入消息队列中。此字符消息将是GetMessage从消息队列中得到的按键消息之后的下一个消息。
在我们处理这个消息时,对应的wParam不是虚拟键,而是ANSI或Unicode字符代码,一般情况下我们可以这样用: (TCHAR)wParam;
4,消息顺序
因为TranslateMessage函数从WM_KEYDOWN和WM_SYSKEYDOWN消息产生了字符消息,所以字符消息是夹在按键消息之间传递给窗口消息处理程序的。例如,如果Caps Lock未打开,而使用者按下再释放A键,则窗口消息处理程序将接收到如表6-10所示的三个消息:
表6-10
如果您按下Shift键,再按下A键,然后释放A键,再释放Shift键,就会输入大写的A,而窗口消息处理程序会接收到五个消息,如表6-11所示:
表6-11
Shift键本身不产生字符消息。
如果使用者按住A键,以使自动重复产生一系列的按键,那么对每条WM_KEYDOWN消息,都会得到一条字符消息,如表6-12所示:
表6-12
如果某些WM_KEYDOWN消息的重复计数大于1,那么相应的WM_CHAR消息将具有同样的重复计数。
组合使用Ctrl键与字母键会产生从0x01(Ctrl-A)到0x1A(Ctrl-Z)的ASCII控制代码,其中的某些控制代码也可以由表6-13列出的键产生:
表6-13
最右列给出了在ANSI C中定义的控制字符,它们用于描述这些键的字符代码。
我们一般可以这样处理WM_CARH消息:
case WM_CHAR:
{
switch (wParam)
{
case 0x08:
// Process a backspace.
break;
case 0x0A:
// Process a linefeed.
break;
case 0x1B:
// Process an escape.
break;
case 0x09:
// Process a tab.
break;
case 0x0D:
// Process a carriage return.
break;
default:
// Process displayable characters.
break;
}
}
我们可以在WM_CHAR里面判断当前是否有指定的键被按下:
BOOL bIsCtrl = (::GetAsyncKeyState(VK_CONTROL) & 0x8000); (MFC源码 afxcolordialog.cpp
460行)
或
BOOL bIsCtrl = (::GetKeyState(VK_CONTROL) & 0x8000);
下面我解释一下键盘消息的lParam参数,这个参数在MSDN上面都可以查到,只是英文,我这里作一些简单的说明:(以WM_KEYDOWN为例)
WPARAM:虚拟键值,VT_*等值。
LPARAM:根据其不同的位数表示的含义不同可以分以下几部分:
(1) 重复计数位(0
- 15 位):表示消息按键数据。一般情况下为1,当键一直按下,窗口过程就会连续收到W_KEYDOWN消息,但有可能窗口过程来不及处理这些按键消息,那么Windows就会把几个按键消息组合成一个,并增加重复计数。比如你处理WM_KEYDOWN时Sleep(200),那么得到的这个数字就可能大于1,一般可以这样来得到这个计数:
DWORD count = (((DWORD)lParam) & 0x0000FFFF);
(2) OEM扫描码(16~23位):OEM扫描码是键盘发送的码值,由于此域是设备相关的,因而此值往往被忽略。
(3) 扩展键标志(24位):扩展键标志在有Alt键(或Ctrl键)按下时为1,否则为0。
(4) 保留位(25~28位):保留位是系统缺省保留的,一般不用。
(5) 关联码(29位):关联码用来记录某键与Alt键的组合状态,若按下Alt,当WM_SYSKEYDOWN消息送到某个激活的窗口时,其值为1,否则为0。
(6) 键的先前状态(位30):键的先前状态用于记录先前某键的状态,对于WM_SYSKEYUP消息,其值始终为1。
(7) 转换状态(31位):转换状态的消息是始终按着某键所产生的消息,若某键原来是按下的,则其先前状态为0。转换状态指示键被按下还是被松开。当键被按下时,对应于者WM_SYSKEYDOWN消息,其值始终为0,当键被松开时,其转换状态为1,对应于WM_SYSKEYUP消息,其值始终为1。
5,死字符消息
Windows程序经常忽略WM_DEADCHAR和WM_SYSDEADCHAR消息,但您应该明确地知道死字符是什么,以及它们工作的方式。
在某些非U.S.英语键盘上,有些键用于给字母加上音调。因为它们本身不产生字符,所以称之为「死键」。例如,使用德语键盘时,对于U.S.键盘上的+/=键,德语键盘的对应位置就是一个死键,未按下Shift键时它用于标识锐音,按下Shift键时则用于标识抑音。
当使用者按下这个死键时,窗口消息处理程序接收到一个wParam等于音调本身的ASCII或者Unicode代码的WM_DEADCHAR消息。当使用者再按下可以带有此音调的字母键(例如A键)时,窗口消息处理程序会接收到WM_CHAR消息,其中wParam等于带有音调的字母「a」的ANSI代码。
因此,使用者程序不需要处理WM_DEADCHAR消息,原因是WM_CHAR消息已含有程序所需要的所有信息。Windows的做法甚至还设计了内部错误处理。如果在死键之后跟有不能带此音调符号的字母(例如「s」),那么窗口消息处理程序将在一行接收到两条WM_CHAR消息-前一个消息的wParam等于音调符号本身的ASCII代码(与传递到WM_DEADCHAR消息的wParam值相同),第二个消息的wParam等于字母s的ASCII代码。
当然,要感受这种做法的运作方式,最好的方法就是实际操作。您必须加载使用死键的外语键盘,例如前面讲过的德语键盘。您可以这样设定:在「控制台」中选择「键盘」,然后选择「语系」页面标签。然后您需要一个应用程序,该程序可以显示它接收的每一个键盘消息的详细信息。下面的KEYVIEW1就是这样的程序。
——————————————————————————————————
怎么判断发来消息时候,是一次新的按键,还是原来的键已经被按下连续发送的?
if(lparam&0x40000000)
新按键
解释如下:
0x40000000是16进制,转成2进制是:01000000000000000000000000000000 注意第30位是1哦
而 if(lParam&0x40000000)判断lParam&0x40000000是否为0,你想想,如果lParam&0x40000000
为0那就证明lParam的第30位是0。为什么?因为只有lParam的第30位是0,才能和0x40000000
的第30位的1通过位与运算得到0,否则,结果就非0。
所以做按位与运算就能知道lParam的第30位是什么内容了。
if(lParam&0x40000000){
.
.
.
}
其实这段程序,是说当lParam的第30位是1的时候,执行以下代码。
同上。
详细一点,第 30 位是表示这个键的之前状态(previous state)是否按下。也就是说,发这个消息来之前,这个键是按下的还是松开的。因为在Windows里面,你按下某个键的时候,系统会发送一个消息;而你按住某个键的时候,系统也会不断地发送这个消息。为了区分这个消息是用户按下某个键还是按住某个键而产生的,就要用到这一位了。在做游戏时应该就要用到这个功能了~~~不过具体该位是用 1 还是 0 表示之前是松开状态的就不清楚了,楼主自己试试。
键盘上每一个键对应一个扫描码,扫描码是OEM厂商制定的,不同的厂商生产的键盘同样一个按键的扫描码都有可能出现不一致的情况,为了摆脱由于系统设备不一致的情况,通过键盘驱动程序将扫描码映射为统一的虚拟键码表示,从而达到所有的设备都有一个统一的虚拟键,比如回车键的虚拟键是VK_RETURN。
Windows定义的虚拟键都定义在WinUser.h这个头文件里面,都是以VK_作为前缀。
2,激活/关闭窗口对键盘的消息
激活/关闭消息:WM_SETFOCUS/WM_KILLFOCUS
创建光标:CreateCaret(...)
设置光标位置:SetCaretPos(…)
在窗口中显示光标:ShowCaret(…)
销毁光标:DestroyCaret()
3,键盘消息
1)字符消息
系统字符消息
WM_SYSCHAR:系统字符
WM_SYSDEADCHAR:系统死字符
非系统按键消息
WM_CHAR:非系统字符
WM_DEADCHAR:非系统死字符
2)按键消息
系统按键消息:与ALT键相组合的组合键(无论用户处理否,都需要最后调用DefWindowProc(hWnd,iMessage,wParam,lParam))
WM_SYSKEYDOWN
WM_SYSKEYUP
非系统按键消息:
WM_KEYDOWN
WM_KEYUP
注意:
a) 除Print键之外都有“按下”消息。
b) 所有键都存在“弹起”消息。
c) 根据MSDN说明,只有下面这些键才会产生字符消息:
任何字符键
回退键(BACKSPACE)
回车键(carriage return)
ESC
SHIFT + ENTER (linefeed 换行)
TAB
我们是怎么收到WM_CHAR的呢?就是因为我们在消息循环时调用了TranslateMessage对键盘消息进行翻译,
如果消息为WM_KEYDOWN或者WM_SYSKEYDOWN,并且按键与位移状态相组合产生一个字符,则TranslateMessage把字符消息放入消息队列中。此字符消息将是GetMessage从消息队列中得到的按键消息之后的下一个消息。
在我们处理这个消息时,对应的wParam不是虚拟键,而是ANSI或Unicode字符代码,一般情况下我们可以这样用: (TCHAR)wParam;
4,消息顺序
因为TranslateMessage函数从WM_KEYDOWN和WM_SYSKEYDOWN消息产生了字符消息,所以字符消息是夹在按键消息之间传递给窗口消息处理程序的。例如,如果Caps Lock未打开,而使用者按下再释放A键,则窗口消息处理程序将接收到如表6-10所示的三个消息:
表6-10
消息 | 按键或者代码 |
WM_KEYDOWN | 「A」的虚拟键码(0x41) |
WM_CHAR | 「a」的字符代码(0x61) |
WM_KEYUP | 「A」的虚拟键码(0x41) |
表6-11
消息 | 按键或者代码 |
WM_KEYDOWN | 虚拟键码VK_SHIFT (0x10) |
WM_KEYDOWN | 「A」的虚拟键码(0x41) |
WM_CHAR | 「A」的字符代码(0x41) |
WM_KEYUP | 「A」的虚拟键码(0x41) |
WM_KEYUP | 虚拟键码VK_SHIFT(0x10) |
如果使用者按住A键,以使自动重复产生一系列的按键,那么对每条WM_KEYDOWN消息,都会得到一条字符消息,如表6-12所示:
表6-12
消息 | 按键或者代码 |
WM_KEYDOWN | 「A」的虚拟键码(0x41) |
WM_CHAR | 「a」的字符代码(0x61) |
WM_KEYDOWN | 「A」的虚拟键码(0x41) |
WM_CHAR | 「a」的字符代码(0x61) |
WM_KEYDOWN | 「A」的虚拟键码(0x41) |
WM_CHAR | 「a」的字符代码(0x61) |
WM_KEYDOWN | 「A」的虚拟键码(0x41) |
WM_CHAR | 「a」的字符代码(0x61) |
WM_KEYUP | 「A」的虚拟键码(0x41) |
组合使用Ctrl键与字母键会产生从0x01(Ctrl-A)到0x1A(Ctrl-Z)的ASCII控制代码,其中的某些控制代码也可以由表6-13列出的键产生:
表6-13
按键 | 字符代码 | 产生方法 | ANSI C控制字符 |
Backspace | 0x08 | Ctrl-H | \b |
Tab | 0x09 | Ctrl-I | \t |
Ctrl-Enter | 0x0A | Ctrl-J | \n |
Enter | 0x0D | Ctrl-M | \r |
Esc | 0x1B | Ctrl-[ |
我们一般可以这样处理WM_CARH消息:
case WM_CHAR:
{
switch (wParam)
{
case 0x08:
// Process a backspace.
break;
case 0x0A:
// Process a linefeed.
break;
case 0x1B:
// Process an escape.
break;
case 0x09:
// Process a tab.
break;
case 0x0D:
// Process a carriage return.
break;
default:
// Process displayable characters.
break;
}
}
我们可以在WM_CHAR里面判断当前是否有指定的键被按下:
BOOL bIsCtrl = (::GetAsyncKeyState(VK_CONTROL) & 0x8000); (MFC源码 afxcolordialog.cpp
460行)
或
BOOL bIsCtrl = (::GetKeyState(VK_CONTROL) & 0x8000);
下面我解释一下键盘消息的lParam参数,这个参数在MSDN上面都可以查到,只是英文,我这里作一些简单的说明:(以WM_KEYDOWN为例)
WPARAM:虚拟键值,VT_*等值。
LPARAM:根据其不同的位数表示的含义不同可以分以下几部分:
(1) 重复计数位(0
- 15 位):表示消息按键数据。一般情况下为1,当键一直按下,窗口过程就会连续收到W_KEYDOWN消息,但有可能窗口过程来不及处理这些按键消息,那么Windows就会把几个按键消息组合成一个,并增加重复计数。比如你处理WM_KEYDOWN时Sleep(200),那么得到的这个数字就可能大于1,一般可以这样来得到这个计数:
DWORD count = (((DWORD)lParam) & 0x0000FFFF);
(2) OEM扫描码(16~23位):OEM扫描码是键盘发送的码值,由于此域是设备相关的,因而此值往往被忽略。
(3) 扩展键标志(24位):扩展键标志在有Alt键(或Ctrl键)按下时为1,否则为0。
(4) 保留位(25~28位):保留位是系统缺省保留的,一般不用。
(5) 关联码(29位):关联码用来记录某键与Alt键的组合状态,若按下Alt,当WM_SYSKEYDOWN消息送到某个激活的窗口时,其值为1,否则为0。
(6) 键的先前状态(位30):键的先前状态用于记录先前某键的状态,对于WM_SYSKEYUP消息,其值始终为1。
(7) 转换状态(31位):转换状态的消息是始终按着某键所产生的消息,若某键原来是按下的,则其先前状态为0。转换状态指示键被按下还是被松开。当键被按下时,对应于者WM_SYSKEYDOWN消息,其值始终为0,当键被松开时,其转换状态为1,对应于WM_SYSKEYUP消息,其值始终为1。
5,死字符消息
Windows程序经常忽略WM_DEADCHAR和WM_SYSDEADCHAR消息,但您应该明确地知道死字符是什么,以及它们工作的方式。
在某些非U.S.英语键盘上,有些键用于给字母加上音调。因为它们本身不产生字符,所以称之为「死键」。例如,使用德语键盘时,对于U.S.键盘上的+/=键,德语键盘的对应位置就是一个死键,未按下Shift键时它用于标识锐音,按下Shift键时则用于标识抑音。
当使用者按下这个死键时,窗口消息处理程序接收到一个wParam等于音调本身的ASCII或者Unicode代码的WM_DEADCHAR消息。当使用者再按下可以带有此音调的字母键(例如A键)时,窗口消息处理程序会接收到WM_CHAR消息,其中wParam等于带有音调的字母「a」的ANSI代码。
因此,使用者程序不需要处理WM_DEADCHAR消息,原因是WM_CHAR消息已含有程序所需要的所有信息。Windows的做法甚至还设计了内部错误处理。如果在死键之后跟有不能带此音调符号的字母(例如「s」),那么窗口消息处理程序将在一行接收到两条WM_CHAR消息-前一个消息的wParam等于音调符号本身的ASCII代码(与传递到WM_DEADCHAR消息的wParam值相同),第二个消息的wParam等于字母s的ASCII代码。
当然,要感受这种做法的运作方式,最好的方法就是实际操作。您必须加载使用死键的外语键盘,例如前面讲过的德语键盘。您可以这样设定:在「控制台」中选择「键盘」,然后选择「语系」页面标签。然后您需要一个应用程序,该程序可以显示它接收的每一个键盘消息的详细信息。下面的KEYVIEW1就是这样的程序。
——————————————————————————————————
怎么判断发来消息时候,是一次新的按键,还是原来的键已经被按下连续发送的?
if(lparam&0x40000000)
新按键
解释如下:
0x40000000是16进制,转成2进制是:01000000000000000000000000000000 注意第30位是1哦
而 if(lParam&0x40000000)判断lParam&0x40000000是否为0,你想想,如果lParam&0x40000000
为0那就证明lParam的第30位是0。为什么?因为只有lParam的第30位是0,才能和0x40000000
的第30位的1通过位与运算得到0,否则,结果就非0。
所以做按位与运算就能知道lParam的第30位是什么内容了。
if(lParam&0x40000000){
.
.
.
}
其实这段程序,是说当lParam的第30位是1的时候,执行以下代码。
同上。
详细一点,第 30 位是表示这个键的之前状态(previous state)是否按下。也就是说,发这个消息来之前,这个键是按下的还是松开的。因为在Windows里面,你按下某个键的时候,系统会发送一个消息;而你按住某个键的时候,系统也会不断地发送这个消息。为了区分这个消息是用户按下某个键还是按住某个键而产生的,就要用到这一位了。在做游戏时应该就要用到这个功能了~~~不过具体该位是用 1 还是 0 表示之前是松开状态的就不清楚了,楼主自己试试。
相关文章推荐
- 扫描
- NFC手机识别身份证的技术实现思路
- 分页技术
- linux软件包管理之一(rpm包管理)
- 使用Pycharm安装Python第三方库
- POJ-1456 Supermarket(贪心,并查集优化)
- Hibernate基本特性二 -- 一级缓存
- Activity启动模式
- POJ-1456 Supermarket(贪心,并查集优化)
- C字符串
- fork系统调用
- Java+MySQL实现网络爬虫程序
- 第三十八课 用break和continue改变流程 【项目1-2】
- iOS开发——单例的实现与使用
- MySql表中key的区别
- DOM扩展之Selectors API
- 并查集
- vc捕获matlab异常
- data-ng-show 指令
- 如何让C#像JavaScript一样编程