上一节中我们得知,Windows系统的按键消息有很多类型,大部分按键消息都是由Windows系统的默认窗口过程处理的,我们自己只需要处理少数几个按键消息。这一节我们将详细讲述Windows系统的所有按键消息及其处理方式。
本节必须掌握的知识点:
系统按键消息和非系统按键消息
虚拟键码
lParam 信息
转移状态
使用按键消息
第30 练:滚动条的键盘接口
5.2.1 系统按键消息和非系统按键消息
■按键消息的分类
|---------|---------------|-------------|
| | 键按下 | 键释放 |
| 非系统按键消息 | WM_KEYDOWN | WM_KEYUP |
| 系统按键消息 | WM_SYSKEYDOWN | WM_SYSKEYUP |
表5-1 按键消息
■非系统按键消息
当我们按下一个键盘按键时,会产生一个WM_KEYDOWN消息,松开按键时,同样也会产生一个按键消息WM_KEYUP。Windows系统会将这两个按键消息送入具有输入焦点的窗口消息队列。通常键按下消息和键释放消息是成对出现的。但是如果你按下一个键不放时,则被认为发生了一次连续按键(自动重复)行为,Windows将发送给窗口过程一连串的 WM_KCEYDOWN(焦点窗口最小化时为WM_SYSKEYDOWN)消息。当此键最终被释放时,Windows发送给窗口过程一个WM_KEYUP(焦点窗口最小化时为WM_SYSKEYUP)消息。像所有的队列消息一样,击键消息是可被实时追踪的。你能通过调用GetMessageTime函数得到键被按下或释放的相对时间。
■系统按键消息
WM_SYSKEYDOWN和WM_SYSKEYUP中的"SYS"代表系统,它表明该击键对 Windows比对Windows应用程序更加重要。当输入键和Alt键组合时通常产生的是 WM_SYSKEYDOWN和WM_SYSKEYUP消息。这些按键调用程序菜单或系统菜单选项,被用来实现系统功能如转换活动窗口(Alt-Tab键或Alt-Esc键),或作为系统菜单快捷键(Alt 键和功能键的组合,如Alt-F4是用于关闭一个应用程序)。应用程序通常忽略 WM_SYSKEYUP和WM_SYSKEYDOWN消息,将它们交付给DefWindowProc函数完成默认处理。因为Windows关注所有的Alt键功能逻辑,应用程序就不必处理这些消息。你的窗口过程最终会接收到的是与击键产生结果相关的消息(如一个菜单被选中)。如果你在窗口过程中代码去捕获这些系统击键消息(就像在本章稍后将介绍的KEYVIEW1和 KEYVIEW2程序中实现的那样),则在处理完毕后,仍然需要发送这些消息给 DefWindowProc函数,以便不影响Windows对它的处理。
【注意】被拦截的系统消息窗口过程处理后,仍然需要交给 Windows 默认的窗口过程DefWindowProc 函数处理,否则将会打断系统消息的传递流程,导致程序错误。
当然如果我们确实想要可以屏蔽所有系统消息,则可以在拦截系统消息后直接返回。可以在窗口过程中添加如下代码:
case WM_SYSKEYDOWN:
case VIM_SYSREYUP:
case WM_SYSCHAR:
return 0 ;
那么在你的程序主窗口具有输入焦点时,就可以有效地阻止所有Alt键的操作。 (WM_SYSCHAR消息将在本章稍后的部分讨论。)这些操作包括Alt-Tab键、Alt-Esc键和菜单操作。虽然你不一定想做这些,但我相信你能感觉到窗口过程内含的强大功能。
不与Alt组合时按下和释放键会产生WM_KEYDOWN和WM_KEYUP消息。应用程序可以使用或者丢弃这些击键消息。Windows也不处理它们。
对所有四类击键消息,wParam是虚拟键代码,用于标识哪个键被按下或被释放,而 IParam包含属于本次击键的一些其他数据。
5.2.2 虚拟键码
■虚拟键码
虚拟键码(Virtual Key Codes)是用于表示键盘上的按键的整数值。在Windows操作系统中,每个按键都被分配了一个唯一的虚拟键码。
虚拟键码由VK_前缀和一个标识符组成,例如VK_A表示字母A键的虚拟键码。
虚拟键码在编程中常用于处理键盘输入。您可以通过捕捉键盘事件并检查事件中的虚拟键码来确定哪个按键被按下或释放。
【注意】虚拟键码是特定于操作系统的。不同的操作系统可能会使用不同的虚拟键码值。上述示例是针对Windows操作系统的常见虚拟键码。
■wParam 参数
虚拟键代码存储在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和 WM_SYSKEYUP消息的wParam参数中。此代码确定哪个键被按下或被释放。
如果你学习过DOS系统16位汇编语言,一定知道键盘扫描码。键盘上的每一个按键都有唯一一个与此对应的扫描码。在IBM兼容键盘上,扫描码16为Q键,17为W键,18为E键,19为R键,20为T 键,21为Y键等。
到了Windows操作系统时代,由于Windows操作系统需要支持全世界几乎所有的语言文字和字符,不同语言版本的Windows操作系统使用的键盘上的字符是不一样的。因此,Windows系统需要支持的"扫描码"要比早期的DOS系统多的多,而且还需要为未来键盘可能需要支持的按键做预留。Windows系统使用了一套与设备无关的方式来处理键盘。至此,我们应该可以理解虚拟键码的真实含义。
大多数虚拟键代码命名是以VK_开头的,它定义在WINUSER.H头文件中。下面这些表中列出了这些虚拟键代码的名称和数值(用十进制和十六进制)以及对应于虚拟键的IBM兼容键盘上的键。同时也指出了哪些键是Windows正常运转中所需要用到的。这些表以十进制顺序列出虚拟键代码。
前四个虚拟键代码中的三个涉及鼠标按钮。
|-----|------|----------------|------|------------|
| 十进制 | 十六进制 | WINUSER.H中的标识符 | 是否必需 | IBM兼容键盘 |
| 1 | 01 | VK_LBUTTON | | 鼠标左键 |
| 2 | 02 | VK_RBUTTON | | 鼠标右键 |
| 3 | 03 | VK_CANCEL | √ | Ctrl-Break |
| 4 | 04 | VK_MBUTTON | | 鼠标中键 |
VK_CANCEL码是唯一的标识同时按下两个键(Ctrl+Break)的虚拟代码。 Windows应用程序通常不使用此键。
【注意】鼠标按键虚拟键码并不会出现在键盘消息中,而是在鼠标消息中。第六章我们将讲述鼠标消息。
以下表中的一些键,如退格键、Tab键、回车键、Esc键和空格键,经常被用于Windows
程序中。但是Windows程序通常使用字符消息(而不是击键消息)来处理这些键。Windows应用程序通常不必去监视Shift键、Ctrl键或Alt键的状态。
|-----|------|----------------|------|------------|
| 十进制 | 十六进制 | WINUSER.H中的标识符 | 是否必需 | IBM兼容键盘 |
| 8 | 08 | VK_BACK | √ | 退格键 |
| 9 | 09 | VK_TAB | √ | Tab键 |
| 12 | 0C | VK_CLEAR | √ | 清除键 |
| 13 | 0D | VK_RETURN | √ | 回车键(任意) |
| 16 | 10 | VK_SHIFT | √ | Shift键(任意) |
| 17 | 11 | VK_CONTROL | √ | Ctrl键(任意) |
| 18 | 12 | VK_MENU | √ | Alt键(任意) |
| 19 | 13 | VK_PAUSE | | Pause键 |
| 20 | 14 | VK_CAPITAL | √ | 大写锁定键 |
| 27 | 1B | VK_ESCAPE | √ | Esc键 |
| 32 | 20 | VK_SPACE | √ | 空格键 |
下表中列出的前八个代码以及VK_INSERT、VK_DELETE码可能是最常使用的虚拟键代码:
|-----|------|----------------|------|--------------|
| 十进制 | 十六进制 | WINUSER.H中的标识符 | 是否必需 | IBM兼容键盘 |
| 33 | 21 | VK_PRIOR | √ | PageUp键 |
| 34 | 22 | VK_NEXT | √ | PageDown键 |
| 35 | 23 | VK_END | √ | End键 |
| 36 | 24 | VK_HOME | √ | HOME键 |
| 37 | 25 | VK_LEFT | √ | 向左箭头键 |
| 38 | 26 | VK_UP | √ | 向上箭头键 |
| 39 | 27 | VK_RIGHT | √ | 向右箭头键 |
| 40 | 28 | VK_DOWN | √ | 向下箭头键 |
| 41 | 29 | VK_SELECT | | |
| 42 | 2A | VK_PRINT | | |
| 43 | 2B | VK_EXCUTE | | |
| 44 | 2C | VK_SNAPSHOT | | PrintScreen键 |
| 45 | 2D | VK_INSERT | √ | Insert键 |
| 46 | 2E | VK_DELETE | √ | Del键 |
| 47 | 2F | VK_HELP | | 假想键 |
Windows也包含了主键盘上的字母键和数字键的虚拟键代码(数字键盘被单独处理):
|-------|-------|----------------|------|---------|
| 十进制 | 十六进制 | WINUSER.H中的标识符 | 是否必需 | IBM兼容键盘 |
| 48-57 | 30-39 | 无 | √ | 主键盘0-9 |
| 65-90 | 41-5A | 无 | √ | A-Z |
【注意】数字键和字母键的虚拟键代码就是ASCII码。Windows程序几乎从来不用这些虚拟键代码,相反这些程序依赖于ASCII字符表示的字符消息。
下面的键是由微软Natural Keyboard键盘及其兼容键盘产生的。
|-----|------|----------------|------|--------------|
| 十进制 | 十六进制 | WINUSER.H中的标识符 | 是否必需 | IBM兼容键盘 |
| 91 | 5B | VK_LWIN | | 左Win键 |
| 9 | 5C | VK_RWIN | | 右Win键 |
| 93 | 5D | VK_APPS | | Application键 |
VK_LWIN和VK_RWIN键被Windows用于打开开始菜单或(在较早的版本中)启动任务管理器。它们也能用于登录或注销Windows(仅在Microsoft Windows NT中),或者是登录或注销网络(用于Windows的工作组版本)。应用程序能通过显示帮助信息或快捷键来处理 Application 键。
下面的代码是和数字小键盘中的键相对应的代码(如果存在的话):
|--------|-------|------------------------|------|---------------------|
| 十进制 | 十六进制 | WINUSER.H中的标识符 | 是否必需 | IBM兼容键盘 |
| 96-105 | 60-69 | VK_NUMPAD0- VK_NUMPAD9 | | NumLock打开时 数字键区0~9 |
| 106 | 6A | VK_MULTIPLY | | 数字键区* |
| 107 | 6B | VK_ADD | | 数字键区+ |
| 108 | 6C | VK_SEPARATOR | | |
| 109 | 6D | VK_SUBTRACT | | 数字键区- |
| 110 | 6E | VK_DECIMAL | | 数字键区. |
| 111 | 6F | VK_DIVIDE | | 数字键区/ |
尽管大部分键盘都有12个功能键,Windows则仅需要10个(F11、F12除外),但它却有24个数字标识符。此外,程序通常把功能键用作键盘快捷键,所以它们通常不处理下表中的击键:
|---------|-------|----------------|------|--------------|
| 十进制 | 十六进制 | WINUSER.H中的标识符 | 是否必需 | IBM兼容键盘 |
| 112-121 | 70-79 | VK_F1-VK_F10 | √ | 功能键F1到F10 |
| 122-135 | 7A-87 | VK_F11-VK_F24 | | 功能键F11-F24 |
| 144 | 90 | VK_NUMLOCK | | 数字锁定键 |
| 145 | 91 | VK_SCROLL | | Scroll Lock键 |
虽然还定义了其他一些虚拟键代码,但它们被保留为非标准键盘上的键或者主机终端 上的键。有兴趣的读者可以自行查阅相关资料,这里不再阐述。
5.2.3 lParam信息
如前所述,在四个按键消息中(WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN、 WM_SYSKEYUP),wParam消息参数包含了虚拟键代码,IParam消息参数包含了帮助理解击键的其他有用信息。32位的lParam消息被分成了 6个字段,如图5-2所示。
图5-2 lParam参数的6个按键消息字段
■重复计数
重复计数是消息所表示的击键的数目。大多数情况下,它被设置为1。但是,如果你按下一个键不放,且窗口过程不足够快,跟不上输入速率(该项可在控制面板的【键盘】应用程序中设置)来处理击键消息,Windows就会把一些WM_KEYDOWN和 WM_SYSKEYDOWN消息合并成一个单独的消息,并相应增加重复计数字段。WM_KEYUP 和WM_SYSKEYUP消息的重复计数总是为1。
重复计数大于1表明此时连续击键的速度快于程序的处理能力,所以你可能想在处理键盘消息的时候忽略重复计数。由于额外的击键堆积,几乎每一个人都有过字处理文档或电子表格不停滚屏的经历。当程序要花费一段时间来处理每一个击键时,应用程序可以忽略重复计数来解决此问题。但是在其他情况下,你也许需要使用重复计数。你可能需要在这两种情况下执行程序,找到最合适的一种。
■OEM 扫描码
OEM扫描码是键盘硬件产生的代码。这对中年的汇编语言程序员来说是相当熟悉的, 他们从PC兼容机的ROM BIOS服务中获得这些值(OEM指的是个人计算机的原始设备制造厂商(Original Equipment Manufacturer),在这里是指"IBM标准")我们不再需要这种东西了。Windows程序几乎可以做到忽略OEM扫描码,除非是它要依赖于键盘上键的分布。
■扩展键标记
如果击键结果来自于IBM加强型键盘的附加键,则扩展键标记为1。(IBM加强型键盘 有101或102个键。键盘上部是功能键。光标移动键与数字小键盘分离,但数字小键盘保留有光标移动键的功能。)键盘右侧的Alt和Ctrl键、分离于数字小键盘的光标移动键(包含 Insert键和Delete键)、数字小键盘的斜线和回车键,以及NumLock键的这一标记位均设置为1。Windows程序通常忽略扩展键标记。
■内容代码
如果在击键的同时也按下了Alt键,则内容代码为1。WM_SYSKEYUP和 WM_SYSKEYDOWN消息的此位始终为1,而WM_KEYUP和WM_KEYDOWN消息的此位始终为0。有两种情况例外。
●如果活动窗口最小化了,则它不具有输入焦点。所有的击键将产生 WM_SYSKEYUP和WM_SYSKEYDOWN消息。如果Alt键未被按下,内容代码 字段将被置为 0。Windows 处理 WM_SYSKEYUP 和 WM_SYSKEYDOWN 消息, 使最小化的活动窗口不处理这些击键。
●在某些非英语的键盘上,一些字符是通过Shift键、Ctrl键或Alt键同另一个键的组合产生的。在这些情况下,内容代码被设置为1,但消息并不是系统击键消息。
■键的先前状态
如果键以前是处于释放状态的,则键的先前状态为0。而如果键以前是按下的,则键的先前状态为1。WM_KEYUP和WM_SYSKEYUP消息的此字段总是为1。但 WM_KEYDOWN和WM_SYSKEYDOWN消息的此字段可能为0或1。该位为1表明,消息为重复击键产生的第二个或后续发出的消息。
■转换状态
如果键正在被按下,转换状态为0。如果键正在被释放,转换状态为1。WM_KEYDOWN和WM_SYSKEYDOWN消息的此字段设置为0,而WM_KEYUP和WM_SYSKEYUP消息的此字段设置为1。
5.2.4 转义状态
■GetKeyState 函数
当处理击键消息时,你可能需要知道是否有转义键(Shift键、Ctrl键和Alt键)或切换键 (CapsLock键、Num Lock键和Scroll Lock键)被按下。你能通过调用GetKeyState函数获得此信息。例如:
iState = GetKeyState(VK_SHIFT);
如果Shift键被按下,则iState变量为负(即高位置1)。如果CapsLock键打开,则从
iState = GetKeystate(VK_CAPITAL);
返回的值最低位置为1。此位与键盘上的小灯保持一致。
GetKeystate函数原型如下:
SHORT GetKeyState(
int nVirtKey //表示要检索状态的虚拟键的虚拟键码
);
函数返回一个SHORT类型的值,表示指定虚拟键的状态。如果返回值的最高位(位15)为1,则表示该键当前按下;如果最高位为0,则表示该键当前释放。
通常你会使用虚拟键代码VK_SHIFT、VK_CONTROL和VK_MENU(你也许还记得指 Alt键)来调用GetKeyState函数。你也能用GetKeyState函数通过标识符VK_LSHIFT、 VK_RSHIFT、VK_LCONTROL、VK_RCONTROL> VK_LMENU 或 VK_RMENU 来确定是左侧还是右侧的Shift键、Ctrl键或Alt键被按下。这些标识符仅在GetKeyState函数和 GetAsyncKeyState函数中使用(下面将详细介绍)。
你也能使用虚拟键代码VK_LBUTTON、VK_RBUTTON和VK_MBUTTON来得到鼠标按钮的状态。但是,大多数需要监视鼠标按钮和击键的Windows 程序通常使用另一种方法,即当Windows程序接收到鼠标消息时,才检查击键。实际上,转义状态信息被包含在鼠标消息中,我们将在下一章介绍。
【注意】GetKeyState函数的用法。**它并非实时地检査键盘状态。**更准确地说,它反映了 到目前为止的键盘状态,并包含了正在被处理的当前消息。大多数情况下,这正是你想要的。如果你需要确定用户是否按下了 Shift+Tab键,可在处理Tab键的WM_KEYDOWN消息时,调用含VK_SHIFT参数的GetKeyState函数。如果GetKeyState函数的返回值是负的,你就知道在按下Tab键之前按下了Shift键。并且在你处理Tab键时,Shift键是否己被释放没有什么影响。你只要知道在Tab键按下的时候,Shift键是按下的。
GetKeyState函数无法让你获得独立于标准键盘消息的键盘信息。例如,你也许感到有 必要暂停窗口过程的处理,直到用户按下F1功能键:
while (GetKeyState(VK_F1) >= 0); // WRONG !!!
这种做法是错误的!这一定会中止你的程序(当然,除非在执行该语句之前,你从消息队列中获得了F1功能键的WM_KEYDOWN消息)。如果你确实需要了解某个键的当前实时状态,可以使用GetAsyncKeyState函数。
■GetAsyncKeyState 函数
GetAsyncKeyState函数用于检索指定虚拟键的状态,包括当前是否按下和之前是否按下。
函数原型如下:
SHORT GetAsyncKeyState(
int vKey //表示要检索状态的虚拟键的虚拟键码
);
函数返回一个SHORT类型的值,表示指定虚拟键的状态。返回值的最高位(位15)表示键的当前状态,如果最高位为1,则表示该键当前按下;如果最高位为0,则表示该键当前释放。返回值的第二高位(位14)表示键的之前状态,如果第二高位为1,则表示该键之前被按下;如果第二高位为0,则表示该键之前被释放。
■GetKeyState 函数与GetAsyncKeyState 函数的区别
GetKeyState函数和GetAsyncKeyState函数都用于检索虚拟键的状态,但它们之间存在一些区别。
●返回值的含义不同:
GetKeyState函数的返回值是一个SHORT类型的值,其中最高位(位15)表示键的当前状态(按下或释放),第二高位(位14)表示键的之前状态。这种返回值的结构使得可以同时获取键的当前状态和之前状态。
GetAsyncKeyState函数的返回值也是一个SHORT类型的值,其中最高位(位15)表示键的当前状态(按下或释放),但没有直接提供之前状态的信息。
●作用范围不同:
GetKeyState函数获取的是当前线程的键盘状态。它返回的是当前线程内最近一次按键的状态,不考虑其他线程或应用程序的按键状态。
GetAsyncKeyState函数获取的是全局的键盘状态。它可以用于检测其他应用程序或窗口中的按键状态。
●对重复按键的处理不同:
GetKeyState函数可以通过返回值中的重复计数字段(位0-15)指示按键是否为重复按下。重复计数为1表示按键是刚刚按下的,重复计数大于1表示按键是重复按下的。
GetAsyncKeyState函数不提供直接的重复计数信息。如果需要处理重复按键,可以在代码中使用额外的逻辑来跟踪按键状态的变化。
5.2.5 使用按键消息
Windows程序忽略了大部分的按键消息,只是处理一些少数按键消息。Windows系统默认窗口过程函数处理WM_SYSKEYDOWN和WM_SYSKEYUP消息,应用程序不必关心它们。如果应用程序处理WM_KEYDOWN消息,通常可以忽略WM_KEYUP消息。
Windows程序通常为不产生字符的击键使用WM_KEYDOWN消息。尽管你认为有可能可以通过使用按键消息和转义状态信息,把击键消息转换为字符,但也不要这么做。你将会在非英语键盘上遇到问题。例如,如果你获得wParam参数等于0x33的 WM_KEYDOWN消息,你知道用户按下了数字键3。到目前为止,一切都还不错。如果你使用GetKeyState函数,且发现Shift键被按下,你也许会认为用户正在输入"#"。未必如此,例如英国用户就是在输入另一种符号,看起来像£。
对光标移动键、功能键、Insert键和Delete键,WM_KEYDOWN消息是最有用的。但是 Insert键、Delete键与功能键,经常被用作菜单快捷键。因为Windows会把菜单快捷键转换为菜单命令消息,所以应用程序也不必自己处理这些按键。
Windows之前的MS-DOS应用程序曾经大量地使用功能键与Shift键、Ctrl键和Alt键 的组合。你能在Windows程序中做类似的事情(的确,Microsoft Word大最地使用了功能键作为快捷命令方式),但不推荐这么做。如果你确实想使用功能键,这些功能键应该重复菜单命令。Windows的目标之一就是提供不需要记忆或查询复杂命令表的用户界面。
因此,总结如下:大部分时间,你仅需要处理光标移动键的WM_KEYDOWN消息,有时处理Insert键和Delete键的WM_KEYDOWN消息。当使用这些键时,可以通过 GetKeyState函数检查 Shift键和Ctrl键的状态。例如,Windows程序经常使用Shift键和光标键的组合来扩大字处理文档中的选中范围。Ctrl键常用于改变光标键的意义。例如,Ctrl 键和右箭头键的组合用于将光标右移一个单词。
决定如何在你的应用程序中使用键盘的一种最好方法是遵循用户的习惯。
5.2.6 第30练:滚动条的键盘接口
/*---------------------------------------------------------
SYSMETS.H -- 系统配置信息结构数组(略)
-----------------------------------------------------------*/
/*------------------------------------------------------------------
030 WIN32 API 每日一练
第30个例子:滚动条的键盘接口
SendMessage函数
(c) www.bcdaren.com, 2020
----------------------------------------------------------------*/
#include <windows.h>
#include "sysmets.h"
LRESULT CALLBACK WndProc(HWND , UINT , WPARAM , LPARAM );
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("SysMets4");
...(略)
return msg.wParam ;
}
//窗口过程
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth;
HDC hdc;
int i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd;
PAINTSTRUCT ps;
SCROLLINFO si; //滚动条参数结构变量
TCHAR szBuffer[10];
TEXTMETRIC tm;
switch (message)
{
case WM_CREATE :
...
return 0;
case WM_SIZE :
...
return 0;
case WM_VSCROLL :
...
return 0;
case WM_HSCROLL :
...
return 0;
//按键消息
case WM_KEYDOWN :
//wParam 指定非系统键的虚拟键码,
//lParam 指定重复次数,扫描码,扩展键标识符,上下文代码,
//前一键状态标识符,以及转换状态标识符。
switch (wParam)
{
case VK_HOME ://HOME键
SendMessage (hwnd, WM_VSCROLL , SB_TOP , 0);//发送滚动条值
break;
case VK_END ://END键
SendMessage (hwnd, WM_VSCROLL , SB_BOTTOM , 0);
break;
case VK_PRIOR ://PageUp键
SendMessage (hwnd, WM_VSCROLL , SB_PAGEUP , 0);
break;
case VK_NEXT ://PageDown键
SendMessage (hwnd, WM_VSCROLL , SB_PAGEDOWN , 0);
break;
case VK_UP ://上箭头键
SendMessage (hwnd, WM_VSCROLL , SB_LINEUP , 0);
break;
case VK_DOWN ://下箭头键
SendMessage (hwnd, WM_VSCROLL , SB_LINEDOWN , 0);
break;
case VK_LEFT ://左箭头键
SendMessage (hwnd, WM_HSCROLL , SB_PAGEUP , 0);
break;
case VK_RIGHT ://右箭头键
SendMessage (hwnd, WM_HSCROLL , SB_PAGEDOWN , 0);
break;
}
return 0;
case WM_PAINT :
...
return 0;
case WM_DESTROY :
PostQuitMessage (0);
return 0;
}
return DefWindowProc (hwnd, message, wParam, lParam);
}
/******************************************************************************
SendMessage函数:将指定的消息发送到一个或多个窗口。
该SendMessage函数的函数调用指定的窗口的窗口过程,并不会返回,直到窗口过程已经处理了该消息。
要发送消息并立即返回,请使用SendMessageCallback或SendNotifyMessage函数。
要将消息发布到线程的消息队列中并立即返回,请使用PostMessage或PostThreadMessage函数。
LRESULT SendMessage(
HWND hWnd, //窗口的句柄,其窗口过程将接收到该消息。
UINT Msg, //要发送的消息。
WPARAM wParam,//其他特定于消息的信息。
LPARAM lParam//其他特定于消息的信息。
);
*/
运行结果:
图5-3 滚动条的键盘接口
总结
第三章SYSMETS程序的3个版本都是在不了解键盘的情况下写的。我们只能通过在滚动条上使用鼠标来滚动文本。上述实例给程序添加键盘接口。
创建键盘接口的一个简单方法是在窗口过程中增加WM_KEYDOWN逻辑,把每一个WM_KEYDOWN消息转换为等同的WM_VSCROLL或 WM_HSCROLL消息,然后调用SendMessage函数将WM_VSCROLL或 WM_HSCROLL消息直接发送给窗口过程。
SendMessage函数用于向指定的窗口发送一个消息,并等待接收方处理完消息后返回。
函数原型如下:
LRESULT SendMessage(
HWND hWnd, //要接收消息的窗口的句柄
UINT Msg, //要发送的消息类型(消息ID)
WPARAM wParam, //消息的附加参数,具体的含义取决于消息类型
LPARAM lParam
);
函数返回一个LRESULT类型的值,表示接收方处理完消息后的返回值。返回值的具体含义取决于发送的消息类型。
我们调用SendMessage函数,将消息发送给指定的窗口。接收方处理完消息后,SendMessage函数会返回接收方的处理结果,我们可以根据返回值进行相应的处理。
需要注意的是,SendMessage函数是同步的,即在消息发送的过程中,发送方会等待接收方处理完消息后才返回。这可能会导致阻塞发送方的线程,直到接收方处理完消息。如果不希望发送方被阻塞,可以考虑使用PostMessage函数发送异步消息。
下面将说明在SYSMETS程序中,我们怎样使用SendMessage函数处理 WM_KEYDOWN 消息:
case WM_KEYDOWN:
switch (wParam)
{
case VK_HOME: //HOME键转换为WM_VSCROLL消息的SB_TOP
SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0) ;
break ;
case VK_END: //HOME键转换为WM_VSCROLL消息的SB_BOTTOM
SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0) ;
break ;
case VK_PRIOR: //HOME键转换为WM_VSCROLL消息的SB_PAGEUP
SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0) ;
break ;