Win32编程之消息机制(二)
一、Windows的消息概念
1.消息组成:
- 窗口句柄
- 消息ID
- 消息的两个参数(两个附带信息)
- 消息产生的时间
- 消息产生时鼠标位置
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG
2.消息的作用
当系统通知窗口工作时,就采用消息的方式派发给窗口
二、消息循环
//消息循环
MSG nMsg = { 0 };
while (GetMessage(&nMsg, NULL, 0, 0)) {
TranslateMessage(&nMsg);//将虚拟消息转换为字符消息
DispatchMessage(&nMsg);//将消息分发给窗口处理函数
}
1.GetMessage():获取本进程的消息
WINUSERAPI BOOL WINAPI
GetMessageA(
_Out_ LPMSG lpMsg,//存放获取到的消息BUFF
_In_opt_ HWND hWnd,//窗口句柄
_In_ UINT wMsgFilterMin,//获取消息的最小ID
_In_ UINT wMsgFilterMax//获取消息的最大ID
);
lpMsg:当获取到消息后,将消息的参数存放到MSG结构中
hWnd:获取到hWnd所指定窗口的消息
wMsgFilterMin和wMsgFilterMax:只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围
2.TranslateMessage():翻译消息,将按键消息翻译成字符消息
BOOL WINAPI
TranslateMessage(
_In_ CONST MSG *lpMsg //要翻译的消息地址
);
检查消息是否有按键消息,如果不是按键消息,不做任何处理,继续执行
3.DispatchMessage()函数执行流程:
DispatchMessage(&nMsg) {
nMsg.hwnd --> 通过窗口句柄找到保存窗口数据的内存 -->然后找到窗口消息处理函数
WndProc(nMsg.hWnd, nMsg.msgID, nMsg.wParam, nMsg.lParam) {
//执行消息处理函数
}
}
每个窗口都必须具有窗口处理函数:
LRESULT CALLBACK WindProc( HWND hWnd, //窗口句柄 UINT msgID, //消息ID WPARAM wParam, //消息参数 LPARAM lParam //消息参数 );
当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数,在窗口处理函数中,不处理的消息,使用缺省窗口处理函数,例如:DefWindowProc
三、创建消息
1.WM_DESTROY
- 产生时间:窗口被销毁时消息
- 附带消息:wParam:为0,IParam:为0
- 一般用法:常用于在窗口被销毁之前,做相应的善后处理,例如资源,内存等
2.WM_SYSCOMMAND消息
- 产生时间:当点击窗口的最大化,最小化,关闭等
- 附带消息:wParam:具体点击的位置,例如关闭SC_CLOSE等,lParam:鼠标光标的位置,LOWORD(lParam)为水平位置,HIWORD(lParam)为垂直位置,lParam低两字节是水平坐标,高两字节是垂直坐标
- 一般用法:常用在窗口关闭时,提示用户处理
3.WM_CREATE消息
- 产生时间:在窗口创建成功但还未显示时
- 附带信息:wParam:为0,lParam:为CREATESTRUCT类型的指针,通过这个指针可以获取CreateWindowEx中的全部12个参数
- 一般用法:常用于初始化窗口的参数,资源等等,包括创建子窗口等
4.WM_SIZE消息
- 产生时间:在窗口的大小发生变化后
- 附带消息:wParam:窗口大小变化的原因,lParam:窗口变化后的大小,LOWORD(lParam)为变化后的宽度,HIWORD(lParam)为变化后的高度
- 一般用法:常用于窗口大小变化后,调整窗口内各个部分的布局
5.WM_QUIT消息
- 产生时间:程序员发送
- 附带消息:wParam:PostQuitMessage函数传递的参数,lParam:0
- 一般用法:用于结束消息循环,当GetMessage收到这个消息后,会返回FALSE,结束while处理,退出消息循环
示例代码:
#include <Windows.h>
#include <stdio.h>
HANDLE g_hOutout = 0;//接收标准输出句柄
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_SIZE: {
short nWidth = LOWORD(lParam);
short nHight = HIWORD(lParam);
char szText[256] = { 0 };
sprintf(szText, "WN_SZIE:宽度:%d,高度:%d\n", nWidth, nHight);
WriteConsole(g_hOutout, szText, strlen(szText), NULL, NULL);
break;
}
case WM_CREATE: {
CREATESTRUCT* pcs = (CREATESTRUCT*)lParam;
char* pszText = (char*)pcs->lpCreateParams;
MessageBox(NULL, pszText, TEXT("提示"), MB_OK);
CreateWindowEx(0, "EDIT", "Hello", WS_CHILD | WS_VISIBLE | WS_BORDER, 0, 0, 200, 200, hWnd, NULL, 0, NULL);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_SYSCOMMAND: {
if (wParam == SC_CLOSE) {
int nRet = MessageBox(hWnd, TEXT("是否退出"), TEXT("提示"), MB_YESNO);
if (nRet == IDYES) {
//什么都不写
}
else {
return 0;
}
}
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR lpCmdLine, int nCmdShow) {
AllocConsole();//增加DOS窗口
g_hOutout = GetStdHandle(STD_OUTPUT_HANDLE);
//设计窗口类
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hInstance = hIns;
wc.lpfnWndProc = WindProc;
wc.lpszClassName = TEXT("Main");
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
//注册窗口类
RegisterClass(&wc);//将以上所有赋值全部写入操作系统
const char* pszText = "hello data";
//在内存中创建窗口
HWND hWnd = CreateWindowEx(0, wc.lpszClassName, TEXT("Window"), WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hIns, (LPVOID)pszText);
//显示窗口
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
//消息循环
MSG nMsg = { 0 };
while (GetMessage(&nMsg, NULL, 0, 0)) {
TranslateMessage(&nMsg);//将虚拟消息转换为字符消息
DispatchMessage(&nMsg);//将消息分发给窗口处理函数
}
return 0;
}
6.WM_PAINT消息
- 产生时间:当窗口需要绘制的时候
- 附带消息:wParam:0,lParam:0
- 专职用法:用于绘图
/*
消息处理步骤
1.开始绘图
HDC BeginPaint(
HWND hWnd; //绘制窗口
LPPAINTSTRUCT lpPaint //绘制参数的BUFF
);//返回绘图设备句柄HDC
2.正式绘图
3.结束绘图
BOOL EndPaint(
HWND hWnd, //绘制窗口
CONST PAINTSTRUCT *lpPaint //绘图参数的指针BeginPaint返回
);
*/
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_PAINT: {
const char* pszText = "WM_PAINT\n";
WriteConsole(g_hOutput, pszText, strlen(pszText), NULL, NULL);
//下述绘图代码,必须放在处理WM_PAINT消息时调用
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 100, 100, "hello", 5);
EndPaint(hWnd, &ps);
break;
}
case WM_LBUTTONDOWN: {
/*
窗口无效区域:需要重新绘制的区域
BOOL InvalidateRect(
HHWND hWnd, //窗口句柄
CONST RECT* lpRect, //区域的矩形坐标
BOOL bErase//重绘前是否先擦除
);
*/
InvalidateRect(hWnd, NULL, TRUE);
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
四、消息循环原理
GetMessage:从系统获取消息,将消息从系统中移除,阻塞函数,当系统无消息时,会等候下一条消息
PeekMessage:以查看的方式从系统获取消息,可以不将消息从系统移除,非阻塞函数,当系统无消息时,返回FALSE,继续执行后续代码
WINUSERAPI BOOL WINAPI
PeekMessageA(
_Out_ LPMSG lpMsg,//message information
_In_opt_ HWND hWnd,//handle to window
_In_ UINT wMsgFilterMin,//first message
_In_ UINT wMsgFilterMax,//last message
_In_ UINT wRemoveMsg//不移除标识:PM_NOREMOVE
);
示例代码:
//消息循环
MSG nMsg = { 0 };
while (true) {
if ((PeekMessage(&nMsg, NULL, 0, 0, PM_NOREMOVE))) {
//有消息
if (GetMessage(&nMsg, NULL, 0, 0)) {
TranslateMessage(&nMsg);
DispatchMessage(&nMsg);
} else {
break;
}
} else {
//空闲处理
WriteConsole(g_hOutout, "onIdle", strlen("onIdle"), NULL, NULL);
}
}
SendMessage:发送消息,会等候消息处理的结果
PostMessage-投递消息,消息发出后立刻返回,不等厚消息执行结果
BOOL SendMessage/PostMessage(
HWND hWnd,//消息发送的目的窗口
UNIT Msg,//消息ID
WPARAM wParam,//消息参数
LPARAM lParam,//消息参数
);
五、消息的分类
- 系统消息:ID范围(0-0x03FF),由系统定义好的消息,可以在程序中直接使用
- 用户自定义消息:ID范围(0x0400-0x7FFF),由用户自己定义,满足用户自己的需求,由用户自己发出消息。并处理,自定义消息宏:WM_USER
1.用户自定义消息
#define WM_MYMESSAGE WM_USER + 1001
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_CREATE: {
PostMessage(hWnd, WM_MYMESSAGE, 1, 2);
break;
}
case WM_MYMESSAGE: {
char szText[256] = { 0 };
sprintf(szText, "自定义消息消息被处理:wParam=%d, lParam=%d\n", wParam, lParam);
MessageBox(hWnd, szText, "提示", MB_OK);
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
六、消息队列
1.消息队列概述
- 消息队列是用来存放消息的队列
- 消息在队列中先入先出
- 所有窗口程序都具有消息队列
- 程序可以从队列中获取信息
- 系统消息队列:由系统维护的消息队列,存放系统产生的消息,例如鼠标,键盘等
- 程序消息队列:属于每一个应用程序(线程)的消息队列,由应用程序(线程)维护
进程产生的消息会先发送到系统的消息队列中,然后系统消息队列根据消息中的窗口句柄,将消息下发到指定窗口的程序消息队列中,然后应用程序通过GeMessage(...)从程序消息队列中获取消息,再通过TranslateMessage(...)翻译,DispatchMessage(...)转发消息,最后执行消息函数WinProc(...);所以,所有的进程消息只要能进入消息队列中,那么一定会先进入系统消息队列中,需要注意的是,PostMessage(...)发送的消息是进入系统消息队列中的。
2.消息和消息队列的关系
当鼠标,键盘产生消息时,会将消息存放到系统消息队列,系统会根据存放的消息,找到对应程序的消息队列,将消息投递到程序的消息队列中。根据消息和消息队列之间使用关系,将消息分成两类:
(1).队列消息:消息的发送和获取,都是通过消息队列完成的,消息发送后,首先放入队列中,然后通过消息循环,从队列当中获取。
GetMessage:从消息队列中获取消息 PostMessage:将消息投递到消息队列中 常见队列消息:WM_PAINT、键盘、鼠标、定时器等
(2).非队列消息:消息的发送和获取,是直接调用消息的窗口处理完成的,消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息。
SendMessage:直接将消息发送给窗口的处理函数,并等候处理结果 常见消息:WM_CREATE、WM_SIZE等
七、GetMessage工作原理
- 在程序(线程)消息队列查找消息,如果队列有消息,检测消息是否满足指定条件(HWND,ID范围),不满足条件就不会取出消息,否则从队列中取出消息并返回
- 如果程序(线程)消息队列中没有消息,向系统消息队列获取属于本程序的消息,如果系统队列的当前消息属于本程序,系统会将消息转发到程序消息队列中
- 如果系统消息队列中也没有消息,检测当前进程的所有窗口的需要重新绘制的区域,如果发现有需要重新绘制的区域,产生WM_PAINT消息,取得消息返回处理
- 如果没有重新绘制区域,检查定时器,如果有到时的定时器,产生WM_TIMER,返回处理执行
- 如果没有到时的定时器,整理程序的资源、内存等
- GetMessage会继续等候下一条消息,PeekMessage会返回FALSE,交出程序的控制权
- 注意:GetMessage如果获取到的是WM_QUIT,函数会返回FALSE
八、键盘消息
1.键盘消息分类
- WM_KEYDOWN:按键被按下时产生
- WM_KEYUP:按键被放开时产生
- WM_SYSKEYDOWN:系统键按下时产生,比如ALT、F10
- WM_SYSKEYUP:系统键放开时产生
附带信息:WPARAM:按键的Virtual Key,LPARAM:按键的参数,例如按下次数
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_KEYDOWN: {
char szText[256] = { 0 };
sprintf(szText, "WM_KEYDOWN:键码值=%d\n", wParam);
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
case WM_KEYUP: {
char szText[256] = { 0 };
sprintf(szText, "WM_KEYUP:键码值=%d\n", wParam);
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
2.字符消息(WM_CHAR)
- TranslateMessage在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR,不可见字符无此消息
- 附带信息:WPARAM:输入的字符的ASCII字符编码值,LPARAM:按键的相关参数
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_CHAR: {
char szText[256] = { 0 };
sprintf(szText, "WM_CHAR:键码值=%d\n", wParam);
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
字符消息工作原理:
TranslateMessage(&nMsg) {
if (nMsg.message != WM_KEYDOWN)
return ..
根据nMsg.wParam(键码值)可以获知哪个按键被按下
if (不可见字符的按键)
return ...
根据CapsLock(大写锁定键)是否处于打开状态
if (CapsLock打开)
PostMessage(nMsg.hwnd, WM_CHAR, 65, ...)//大写字符的ASCII码
else
PostMessage(nMsg.hwnd, WM_CHAR, 97, ...)//小写字符的ASCII码
}
九、鼠标消息
1.鼠标基本消息
WM_LBUTTONDOWN:鼠标左键按下 WM_LBUTTONUP:鼠标左键抬起 WM_RBUTTONDOWN:鼠标右键按下 WM_RBUTTONUP:鼠标右键抬起 WM_MOUSEMOVE:鼠标移动消息
附带信息 :
wPARAM:其他按键的状态,例如Ctrl/Shift等 IPARAM:鼠标的位置,窗口客户区坐标系,LOWORD:X坐标位置,HIWORD:Y坐标位置 一般情况下,鼠标的按下/抬起成对出现,在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息
示例代码:
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_MOUSEMOVE: {
char szText[256] = { 0 };
sprintf(szText, "WM_MOUSEMOVE:其他按键状态:%d, X=%d, Y=%d\n",
wParam, LOWORD(lParam), HIWORD(lParam));
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
case WM_LBUTTONDOWN: {
char szText[256] = { 0 };
sprintf(szText, "WM_LBUTTONDOWN:其他按键状态:%d, X=%d, Y=%d\n",
wParam, LOWORD(lParam), HIWORD(lParam));
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
case WM_LBUTTONUP: {
char szText[256] = { 0 };
sprintf(szText, "WM_LBUTTONUP:其他按键状态:%d, X=%d, Y=%d\n",
wParam, LOWORD(lParam), HIWORD(lParam));
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
2.鼠标双击消息:
WM_LBUTTONDBLCLK:鼠标左键双击 WM_RBUTTONDBLCLK:鼠标右键双击
附带信息:
wPARAM:其他按键的状态,例如Ctrl/Shift等 IPARAM:鼠标的位置,窗口客户区坐标系,LOWORD:X坐标位置,HIWORD:Y坐标位置
消息产生顺序:
以左键双击为例: WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK WM_LBUTTONUP 使用时需要在注册窗口类的时候添加CS_DBLCLKS风格
示例代码:
#include <Windows.h>
#include <stdio.h>
HANDLE g_hOutput = 0;
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_LBUTTONDBLCLK: {
char szText[256] = { 0 };
sprintf(szText, " WM_LBUTTONDBLCLK:\n");
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
case WM_LBUTTONDOWN: {
char szText[256] = { 0 };
sprintf(szText, "WM_LBUTTONDOWN:其他按键状态:%d, X=%d, Y=%d\n",
wParam, LOWORD(lParam), HIWORD(lParam));
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
case WM_LBUTTONUP: {
char szText[256] = { 0 };
sprintf(szText, "WM_LBUTTONUP:其他按键状态:%d, X=%d, Y=%d\n",
wParam, LOWORD(lParam), HIWORD(lParam));
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR lpCmdLine, int nCmdShow) {
AllocConsole();
g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设计窗口类
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hInstance = hIns;
wc.lpfnWndProc = WindProc;
wc.lpszClassName = TEXT("Main");
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
//注册窗口类
RegisterClass(&wc);//将以上所有赋值全部写入操作系统
//在内存中创建窗口
HWND hWnd = CreateWindowEx(0, wc.lpszClassName, TEXT("Window"), WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hIns, NULL);
//显示窗口
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
//消息循环
MSG nMsg = { 0 };
while (true) {
if ((PeekMessage(&nMsg, NULL, 0, 0, PM_NOREMOVE))) {
//有消息
if (GetMessage(&nMsg, NULL, 0, 0)) {
TranslateMessage(&nMsg);
DispatchMessage(&nMsg);
} else {
break;
}
}
}
return 0;
}
3.鼠标滚轮消息
WM_MOUSEWHEEL:鼠标滚轮消息
附带信息:
wPARAM:LOWORD:其他按键的状态,HIWORD:滚轮的偏移量,通过正负值表示滚动方向,正:向前滚动,负:向后滚动 IPARAM:鼠标当前的位置,屏幕坐标系,LOWORD:X坐标,HIWORD:Y坐标 使用:通过偏移量,获取滚动的方向和距离
示例代码:
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_MOUSEWHEEL: {
short nDelta = HIWORD(wParam);//偏移量
char szText[256] = { 0 };
sprintf(szText, "WM_MOUSEWHEEL:nDetal=%d\n", nDelta);
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}
十、定时器消息
在程序中创建定时器,当到底时间间隔时,定时器会向程序发送一个WM_TIMER消息。时器的精度是毫秒,但是准确度很低,例如设置时间间隔为1000ms,但是会在非1000毫秒到底消息。附带消息:wPARAM:定时器ID,IPARAM:定时器处理函数的指针
1.创建定时器
UINT_PTR SetTimer( HWND hWnd,//定时器窗口句柄 UINT_PTR nIDEvent,//定时器ID UINT uElapse,//时间间隔 TIMERPPOC lpTimerFunc//定时器处理函数指针(一般不使用,为NULL) ); 定时器创建成功后,返回非0
示例代码:
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WindProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam) {
switch (msgID) {
case WM_CREATE: {
SetTimer(hWnd, 1, 1000, NULL);
SetTimer(hWnd, 2, 2000, NULL);
break;
}
case WM_TIMER: {
char szText[256] = { 0 };
sprintf(szText, "WM_TIMER:定时器ID=%d\n", wParam);
WriteConsole(g_hOutput, szText, strlen(szText), NULL, NULL);
KillTimer(hWnd, wParam);//销毁定时器
break;
}
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}

浙公网安备 33010602011771号