消息结构体
如下是消息的结构体
typedef struct tagMSG
{
HWND hwnd; // 消息所属窗口的句柄
UINT message; // 消息的标识符,表示什么类型的消息,如WM_PAINT、WM_QUIT等。
WPARAM wParam; // 与消息相关的附加信息。具体含义取决于消息的类型。
LPARAM lParam; // 与消息相关的附加信息。具体含义取决于消息的类型。
DWORD time; // 消息被创建的时间(系统启动后的毫秒数)。
POINT pt; // 消息发生时的光标位置,相对于屏幕的坐标。
DWORD lPrivate; // 私有字段,用于操作系统内部使用,应用程序通常不使用此字段。
} MSG, *PMSG, *NPMSG, *LPMSG;
我们通过观察消息的结构体可知:窗口过程函数的四个参数就是MSG消息结构体前四个字段MSG里的time和pt成员是给操作系统用的,操作系统使用到了这些成员,放置到相应线程的消息队列中。
消息的产生
当我们对某个窗口进行操作时,这个行为就会产生一个消息。操作系统最先得到这个消息,之后会判断这个消息要发送给哪个窗口,操作系统找到窗口后将这个消息放到这个窗口对象所属线程的消息队列中。应用层的GetMessage不停地从消息队列中取消息,取出来的消息放在MSG结构体中
消息的处理流程
我们对窗口的任意操作,实际上就是一个消息。从MSG结构体中的HWND成员可以看出,消息是针对窗口的。但MSG消息结构体中只有窗口对象的句柄HWND,没有窗口过程函数的信息。因此操作系统就需要调用DispatchMessage(&msg)函数根据句柄HWND找到窗口过程函数(根据HWND进入内核查找全局窗口句柄表,找到窗口对象,找到这个窗口对象的窗口过程函数)。最后由内核发起调用,执行窗口过程函数,将消息处理掉。
下图便是消息处理的过程:
消息类型
接下来我们以一个空白窗口过程函数,逐步优化进行讲解
LRESULT CALLBACK WindowProc(HWND hwnd, // 窗口句柄
UINT uMsg, // 消息标识
WPARAM wParam, // 附加消息信息
LPARAM lParam) // 附加消息信息
{
return DefWindowProc(hwnd, uMsg, wParam, lParam); // 默认窗口处理函数
}
能产生消息的四种情况:鼠标,键盘,其他的应用程序,操作系统的内核程序。每种消息都有一个编号,对应的字段是MSG.message
现在我们在窗口过程函数中打印uMsg字段,体会下消息
LRESULT CALLBACK WindowProc(HWND hwnd, // 窗口句柄
UINT uMsg, // 消息标识
WPARAM wParam, // 附加消息信息
LPARAM lParam) // 附加消息信息
{
char szOutBuff[0x80];
sprintf(szOutBuff, "Message: %x - %x \n", hwnd, uMsg);
OutputDebugString(szOutBuff);
return DefWindowProc(hwnd, uMsg, wParam, lParam); // 默认窗口处理函数
}
此时运行程序,但我们并不对窗口进行操作,发现消息是不停的产生的。这是因为除了用户的鼠标,键盘,还可能有其他的应用程序,操作系统内核程序发的消息。
在上图中,每个uMsg字段都对应着一种消息,比如1就是对应WM_CREATE,意思就是窗口被创建。
接下来,我们开始讲解不同的消息
窗口关闭消息
我们可以对窗口进行拖动,最小化,最大化等操作,这是因为程序用了默认窗口过程函数来处理了这些消息DefWindowProc(hwnd, uMsg, wParam, lParam);
。而在实际开发中,我们只需要针对自己需要的消息进行处理即可,其他的就交给默认的窗口过程函数。
窗口退出的消息是WM_DESTROY,现让我们对该消息进行填写:
LRESULT CALLBACK WindowProc(HWND hwnd, // 窗口句柄
UINT uMsg, // 消息标识
WPARAM wParam, // 附加消息信息
LPARAM lParam) // 附加消息信息
{
switch (uMsg)
{
case WM_DESTROY: // 当窗口关闭则退出进程
{
PostQuitMessage(0);
break;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam); // 默认窗口处理函数
}
在上图代码中case WM_DESTROY的内容便是我们做出的针对窗口关闭的处理代码。当用户点击窗口关闭按钮时,进程退出。如果没有这段代码,关闭窗口就只是关闭窗口,进程仍然存在
键盘消息
键盘按下消息是WM_KEYDOWN。
当uMsg是WM_KEYDOWN消息时:
wParam:非系统键的虚拟键代码,用来记录按下哪一个键
lParam:32位大小,包含了与按键事件相关的额外信息:
0-15位:重复计数(表示自上次消息以来按键被按下的次数)
16-23位:扫描码(表示按键的硬件扫描码)
24位:扩展键标志(如果是由扩展键生成的,则该位为1)
25-28位:保留,不使用
29位:上下文代码(如果在中断时发出,则该位为1)
30位:先前的键状态(如果按键之前已经按下,则为1;如果之前未按下,则为0)
31位:转换状态(如果消息是由转换状态(例如将非字符键转换为字符键)生成的,则该位为1)
注意:键盘原生的消息是虚拟键消息。如果想得到字符消息,那么在DispatchMessage分发消息前需要调用TranslateMessage进行消息转换
WM_KEYDOWN和WM_CHAR区别:
WM_KEYDOWN
和WM_CHAR
是Windows编程中用于处理键盘输入的两种不同类型的消息,它们在处理键盘事件时有着明显的区别:
WM_KEYDOWN:
1.这是一个虚拟键消息,当用户按下键盘上的任何键时,就会触发WM_KEYDOWN
消息
2. 它提供了虚拟键码(wParam
参数),表示被按下的键。例如,如果按下回车键,虚拟键码将是VK_RETURN
3. WM_KEYDOWN
消息用于处理键盘上的非字符键(如功能键、控制键等)以及字符键
4. lParam
参数提供了关于按键事件的额外信息,如按键的重复计数、扫描码、是否是扩展键等
WM_CHAR:
1.这是一个字符消息,当用户按下能产生字符的键时,就会触发WM_CHAR
消息。
2.WM_CHAR
消息通过wParam
参数提供实际的字符代码,这通常对应于按键生成的字符,考虑了键盘布局、按下的是大写键还是小写键、是否同时按下了Shift键等。
3.WM_CHAR
适合处理可打印字符的输入,如文本输入。不会为非字符键生成WM_CHAR
消息。
4.与WM_KEYDOWN
不同,WM_CHAR
不关心按键的物理位置或特定的硬件键
总的来说,WM_KEYDOWN
消息更多地关注物理键盘上的键的状态,而WM_CHAR
消息关注的是这些按键操作所产生的字符。在编写需要处理文本输入的应用程序时,通常会更多地关注WM_CHAR
消息,因为它提供了对应于用户按键的实际字符。而WM_KEYDOWN
则适用于那些需要处理非字符键(如方向键、功能键等)的场景。
更多的消息
case WM_CREATE:窗口创建时触发的消息
case WM_MOVE:窗口移动时触发的消息
case WM_SIZE:窗口大小改变时触发的消息
case WM_DESTROY:窗口关闭时触发的消息
case WM_KEYUP:键盘键释放时触发的消息
case WM_KEYDOWN:键盘键按下时触发的消息
case WM_LBUTTONDOWN:鼠标左键按下时触发的消息
case WM_COMMAND:鼠标左键点击控件