《Windows API每日一练》5.2 按键消息
上一节中我们得知,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 ;
相关文章:

《Windows API每日一练》5.2 按键消息
上一节中我们得知,Windows系统的按键消息有很多类型,大部分按键消息都是由Windows系统的默认窗口过程处理的,我们自己只需要处理少数几个按键消息。这一节我们将详细讲述Windows系统的所有按键消息及其处理方式。 本节必须掌握的知识点&…...
adb 截屏和录屏命令
adb 录屏命令 screenrecord 简介 screenrecord 是一个 shell 命令 支持 Android 4.4(API level 19)以上 支持视频格式: mp4 一些限制 某些设备可能无法直接录制,原因是分辨率太高,如果遇到此类问题,请试着指定较低的分辨率 不支持录制过程中屏幕旋转,如果录制…...

springboot相关的一些知识
SpringBoot可以同时处理多少请求 SpringBoot默认的内嵌容器是Tomcat,所以SpringBoot可以同时处理多少请求取决于Tomcat。 SpringBoot中处理请求数量相关的参数有四个: server.tomcat.thread.min-spare:最少的工作线程数,默认大小…...

DP:完全背包+多重背包问题
完全背包和01背包的区别就是:可以多次选 一、完全背包(模版) 【模板】完全背包_牛客题霸_牛客网 #include <iostream> #include<string.h> using namespace std; const int N1001; int n,V,w[N],v[N],dp[N][N]; //dp[i][j]表示…...
购物返利系统的安全性:防范欺诈与数据保护
购物返利系统的安全性:防范欺诈与数据保护 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 购物返利系统作为一种电子商务模式,通过向消…...

从WebM到MP3:利用Python和wxPython提取音乐的魔法
前言 有没有遇到过这样的问题:你有一个包含多首歌曲的WebM视频文件,但你只想提取其中的每一首歌曲,并将它们保存为单独的MP3文件?这听起来可能有些复杂,但借助Python和几个强大的库,这个任务变得异常简单。…...

图片转pdf,图片转pdf在线转换,在线图片转pdf
图片转PDF,听起来似乎是一个简单的操作,但实际上,它涉及到许多细节和技巧。有时候我们需要将图片转换为PDF格式,以便于分享、打印或保存。那么,如何将图片转换成PDF呢?接下来,我将为您详细介绍几…...
SpringBoot3使用Swagger3
SpringBoot3使用Swagger3 项目中的后端接口进行简单的前端展示一、依赖引入二、快速启动1.在application.yml中配置2.或者properties文件,则配置3.启动项目访问swagger 三、使用注解标注接口Swagger配置文件Swagger 注解迁移举例五种常用ApiApiOperationApiImplicitParamApiMod…...

【51单片机基础教程】点亮led
文章目录 前言51单片机点亮LED的原理硬件部分软件部分51单片机的寄存器编程步骤proteus仿真点亮一个led 点亮多个ledproteus仿真代码 流水灯 总结 前言 单片机(Microcontroller Unit, MCU)是一种集成电路,广泛应用于各种电子产品中。作为嵌入…...

Docker之overlay2的迁移
原因 docker默认将文件及其容器放置在了系统盘的挂载区内,如果长期使用会发现系统挂载区被overlay2挤爆了,因此在一开始我们将其迁移在大容量外挂磁盘上,就可以避免系统盘被挤爆,放心使用. 具体操作 # 停止容器 systemctl stop docker# 修改容器配置,…...
CentOS中的rename命令
目录 CentOS中的rename命令基本语法使用示例注意事项安装prename CentOS中的rename命令 在CentOS系统中,rename命令通常是指util-linux包中提供的版本,它用于批量重命名文件,但与Perl版本的rename命令相比,功能较为简单ÿ…...

redis.conf 参数详解,方便进行性能优化配置
以下是redis.conf中一些常见参数的详细说明: daemonize:是否以后台进程运行,默认为no; pidfile:如以后台进程运行,则需指定一个pid,默认为/var/run/redis.pid;bind:绑定主…...

微信小程序登录流程详情及Java代码
一、流程图 说明: 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。 获取手机号,调用wx.getPhoneNumber() ,获取加密…...

c++qt合并两张灰度图像
需求:将两张尺寸相同的灰度图像进行合并,合并后的图像,每个像素点灰度值为两张原图对应像素点灰度值之和。若超过255,则最大为255。 方法一: 将图像读取为cv::Mat,再调用opencv的cv::add方法,进…...

Uniapp通过年月日时间转变星期格式
效果图 参靠微信小程序:日常记一记 代码 <view v-for"(d,index) in dataList" >{{getWeekDay(d.ctime)}} //时间格式:2024-06-21</view> js export default {data(){return {dataList:[],//时间数组}},onLoad() {this.loadList…...
如何编写和执行高效的测试计划
如何编写和执行高效的测试计划 1. 测试计划概述2. 测试阶段详解3. 测试计划模板4. 关键注意事项总结 1. 测试计划概述 测试计划是指导整个测试过程的重要文档,其中包含了测试策略、资源分配、进度安排以及风险评估等内容。 一个完善的测试计划应当包括以下几个主要…...

【MySQL连接器(Python)指南】03-MySQL连接器(Python)安装
文章目录 前言1. 从二进制发行版中安装连接器1.1 使用pip安装MySQL连接器1.2 使用MySQL Yum Repository安装1.3 使用Debian软件包安装连接器2. 从源代码发行版安装连接器2.1 在Windows上源码安装2.2 在类Unix系统上源码安装3. 验证连接器安装总结前言 MySQL连接器(Python),用于…...

Spring Boot组件化与参数校验
Spring Boot组件化与参数校验 Spring Boot版本选择 2.3.x版本 2.6.x版本 Spring Boot核心思想 约定大于配置,简化繁琐的配置 Spring Boot自动配置原理 SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,Spr…...
实现可扩展的电商返利平台:技术选型与挑战
实现可扩展的电商返利平台:技术选型与挑战 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 在当今数字化和电商兴盛的时代,返利平台成为…...

从0开始C++(三):构造函数与析构函数详解
目录 构造函数 构造函数的基本使用 构造函数也支持函数重载 构造函数也支持函数参数默认值 构造初始化列表 拷贝构造函数 浅拷贝和深拷贝 析构函数 总结 练习一下ヽ( ̄▽ ̄)ノ 构造函数 构造函数的基本使用 构造函数是一种特殊的成…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

数据分析六部曲?
引言 上一章我们说到了数据分析六部曲,何谓六部曲呢? 其实啊,数据分析没那么难,只要掌握了下面这六个步骤,也就是数据分析六部曲,就算你是个啥都不懂的小白,也能慢慢上手做数据分析啦。 第一…...

Selenium 查找页面元素的方式
Selenium 查找页面元素的方式 Selenium 提供了多种方法来查找网页中的元素,以下是主要的定位方式: 基本定位方式 通过ID定位 driver.find_element(By.ID, "element_id")通过Name定位 driver.find_element(By.NAME, "element_name"…...