在本章前面讲过,通过转义状态信息可把击键消息转换为字符消息。并且提到,仅仅利用转义状态信息是不够的。还必须知道与国家/地区相关的键盘配置。其实不必担心这个问题。Windows程序主程序中的消息循环可以完美的解决这个问题。
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
这是WinMain函数中典型的消息循环。GetMessage函数从消息队列中取出下一条消息,填入msg结构的字段。DispatchMessage函数调用此消息的窗口过程。
在这两个函数之间是TranslateMessage函数,它负责把击键消息转换为字符消息。如果击键消息是WM_KEYDOWN或WM_SYSKEYDOWN,且击键和转义状态组合产生了一个字符,则TranslateMessage函数把字符消息放入应用程序的消息队列。这个字符消息将被放在按键消息之后,GetMessage函数可从消息队列中获取此字符消息。
本节必须掌握的知识点:
四类字符消息
消息排序
控制字符的处理
死字符处理
5.3.1 四类字符消息
字符消息可分为四类。
|-------|------------|----------------|
| | 字符 | 死字符 |
| 非系统字符 | WM_CHAR | WM_DEADCHAR |
| 系统字符 | WM_SYSCHAR | WM_SYSDEADCHAR |
WM_CHAR消息和WM_DEADCHAR消息来自于WM_KEYDOWN消息。而 WM_SYSCHAR 消息和 WM_SYSDEADCHAR 消息来自于 WM_SYSKEYDOWN 消息。
大多数情况下,Windows程序会忽略其他三种字符消息,仅处理 WM_CHAR消息。四类字符消息中的lParam参数和产生此字符码消息的按键消息(WM_KEYDOWN)中的 IParam参数是一样的(重复计数、扫描代码、扩展键标志、上下文代码、上一个键状态标志和转换状态标志)。但是,WM_CHAR消息的wParam参数不是虚拟键代码。实际上,它是ANSI或Unicode 字符码。
这些字符消息是我们遇到的将文本传递给窗口过程的第一个消息。但它们绝不是唯一 的一个。其他字符消息是将以零结束的文本串传递给窗口过程的消息。窗口过程怎么知道哪些字符数据是8位的ANSI字符编码,哪些是16位的Uincode字符编码呢?很简单,任何窗口过程,只要包含用RegisterClassA(RegisterClass的ANSI版本)注册的窗口类,就得到包含ANSI字符编码的消息。若包含用RegisterClassW(RegisterClass的宽字符版本)注册的窗口类,则得到Unicode字符编码的消息。如果你的程序用RegisteiClass注册窗口类,且Unicode标识符被定义,则你使用的实际是RegisterClassW,否则为RegisterClassA。
除非你明确地对窗口过程做了 ANSI和Unicode函数的混合编码,否则WM_CHAR消息(以及其他三个字符消息)所传递的字符码是:
(TCHAR) wParam
同一个窗口过程可能会用到两个窗口类,一个用RegisterClassA注册,另一个用 RegisterClassW注册。这意味着窗口过程会接收到用ANSI字符编码的消息和用Unicode字 符编码的消息。如果你的窗口过程需要确认是哪种字符编码,它能调用:
fUnicode = IsWindowUnicode(hvmd);
如果hwnd的窗口过程获得Unicode消息,则fUnicode变量为TRUE。这意味着此窗口基于用RegisterClassW注册的窗口类。
5.3.2 消息排序
因为 TranslateMessage 函数从 WM_KEYDOWN 和 WM_SYSKEYDOWN 消息产生字符消息,所以字符消息夹在击键消息中传给窗口过程。例如,如果CapsLock键没有锁定, 则在你按下再释放A键时,相应的窗口过程会接收以下三个消息:
|------------|----------------|
| 消息 | 按键或代码 |
| WM_KEYDOWN | 'A'的虚拟键码(0x41) |
| WM_CHAR | 'a'的字符编码(0x61) |
| WM_KEYUP | 'A'的虚拟键码(0x41) |
如果你通过以下几步输入大写字母A:按下Shift键,再按下A键,释放A键,再释放Shift 键,则窗口过程接收五个消息:
|------------|--------------------|
| 消息 | 按键或代码 |
| WM_KEYDOWN | 虚拟键码VK_SHIFT(0x10) |
| WM_KEYDOWN | 'A'的虚拟键码(0x41) |
| WM_CHAR | 'A的字符编码(0x41) |
| WM_KEYUP | 'A'的虚拟键码(0x41) |
| WM_KEYUP | 虚拟键码VK_SHIFT(0x10) |
【注意】Shift键本身不会产生字符消息。
如果你持续按住A键,使连续击键行为产生了击键消息,则对于每一条WM_KEYDOWN 消息,可以得到一个字符消息:
|------------|----------------|
| 消息 | 按键或代码 |
| 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) |
如果某些WM_KEYDOWN消息的重复计数大于1,则相应的WM_CHAR消息将具有同样的重复计数值。
Ctrl键和字母键的组合会产生ASCII控制字符,范围从0x01(Ctrl-A)到0x1A(Ctrl_Z)。其中的某些控制码也可由下表中的按键产生(win10系统环境下已经不同于winNT或98):
|---------|---------|----------|--------------------|
| 按键 | 字符码 | 产生方法 | ANSI C 转义码 |
| 空格键 | 0x08 | Ctrl-H | \b |
| Tab键 | 0x09 | Ctrl-I | \t |
| Ctrl+回车 | 0x0A | Ctrl-J | \n |
| 回车键 | 0x0D | Ctrl-M | \r |
| Esc键 | 0x1B | Ctrl-[ | |
最右面一列显示了在ANSI C中定义的转义码,用来表明这些键的字符码。
Windows程序有时采用Ctrl键和字母键的组合作为菜单快捷键(我们将在第九章中讨 论)。在这种情况下,字母键不能转换为字符消息。
5.3.3 控制字符的处理
我们按照以下的基本规则来处理击键和字符消息:如果你需要读取输入到窗口中的键 盘字符,就处理WM_CHAR消息:如果你需要读取光标键、功能键、Delete键、Insert键、 Shift键、Ctrl键和Alt键,则处理WM_KEYDOWN消息。
但是Tab键怎么办?回车键、空格键、Esc键呢?从传统意义上讲,这些键产生ASCII 控制字符,像上表中所显示的那样。但是在Windows中,它们也产生虚拟键代码。这些键应该在处理WM_CHAR消息或者在处理WM_KEYDOWN消息时被处理吗?
通常我们更偏向把Tab键、回车键、空格键 和Esc键看作控制字符,而不是虚拟键。我通常这样处理WM_CHAR消息:
case WM_CHAR:
//其他行程式
switch (wParam)
{
case '\b': // backspace
//其他行程式
break ;
case '\t': // tab
//其他行程式
break ;
case '\n': // linefeed
//其他行程式
break ;
case '\r': // carriage return
//其他行程式
break ;
default: // character codes
//其他行程式
break ;
}
return 0 ;
5.3.4 死字符处理
Windows程序通常忽略WM_DEADCHAR和WM_SYSDEADCHAR消息,但是你应该明确地了解什么是死字符和它们怎样工作的。
在一些非美国英语的键盘上,某些键可以给字母加上音调。这些键称为"死键",因为它们自己不产生字符。例如,当安装了德语键盘时,对于美国式键盘上的+/=键,德语键盘上相应的位置为一个死键:
未按下 Shift 键时它用于标识锐音,按下 Shift 键时则用于标识抑音。
当用户按下此键时,相应的窗口过程会接收到wParam参数等于音调本身的ASCII或 Unicode码的WM_DEADCHAR消息。若接着按下可带有音调的字母键时(例如A键),窗口 过程接收到的将是wParam参数等于带有音调的字母'a'的ANSI码的WM_CHAR消息。
因而,你的程序不必去处理WM_DEADCHAR消息,因为WM_CHAR消息已包含了程序所需要的所有信息。Windows程序甚至有内置的差错处理:如果你按下死键,接着按下不能携带音调的字母键(如's'键),那么窗口过程会连续接收两个WM_CHAR消息。第一个消息的wParam参数等于音调本身的ASCII码(与传递给WM_DEADCHAR消息的 wParam参数相同),第二个消息的wParam参数等于字母's'的ASCII码。
当然,理解它的最好方式就是实际操作。你需要加载使用死键的非英语键盘,比如我前面描述过的德国键盘。你可在控制面板中通过选择【键盘】中的【语言】选项卡来设置它。然后你需要一个应用程序,它能显示程序接收的每一个键盘消息的细节。下面将要讨论的KEYVIEW1就是这样的一个程序。