1.消息的产生与处理流程:
如上图,我们在创建窗口程序中详细介绍了这张图。比如,我们点击某个窗口的时候就会产生消息, *** 作系统会先判断这个消息是点在了哪个窗口,找到窗口后,会根据窗口对象中的一个成员找到窗口是属于哪个线程的。找到后就会把他封装好的消息放到一个结构体中,然后存到消息队列中,那么我们的GetMessage()就会不停地把消息队列中的消息取出。
在之前我们创建窗口程序的代码中有(我是截取的)
MSG msg; GetMessage(&msg, NULL, 0, 0)
我们知道取出的消息放到了msg中.我们转到定义看看MSG
注意:能产生消息的四种情况:1.鼠标 2.键盘 3.其他应用程序 4. *** 作系统的内核程序
那么这么多消息 *** 作系统怎么处理呢? *** 作系统会把他们分成不同类别,每种不同的消息给他们一个唯一的编号。下面介绍参数
UINT message 这个参数是UINT型,它给每个消息类型一个编号,比如我点一下鼠标就会有个唯一类型的编号。 HWND hwnd 句柄,消息是基于窗口的,任何消息都是某一个窗口的消息。 WPARAM wParam; LPARAM lParam; 这两个成员是描述你的消息是什么样的 DWORD time 消息什么时候产生的 POINT pt 你的消息什么位置产生的
那么我们取出消息放到msg中为什么不直接处理呢?其实之前的学习中我们也提过了。现在我们再看一下MSG,通过我们只能知道,是哪个窗口什么类型的。我们得到的仅仅是一个索引,我们不知道窗口回调也就是窗口处理函数是什么,所以把得到的message传给DispatchMessage()它拿着消息和HWND,进入内核,内核会根据句柄找到窗口对象找到窗口处理函数,再由内核发起调用执行窗口过程(处理)函数。
经过上面的介绍我们现在知道消息处理函数中的四个参数是从哪里来的了。
这是窗口过程的四个参数 _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam
我们通过GetMessage得到消息,这四个参数全在MSG中,然后把MSG(消息)通过DispatchMessage()传到内核,内核在调用窗口过程的时候又会把这四个参数传回来(详细可看最上面的那张图).所以窗口过程函数中的四个参数就是MSG中的四个成员。
那么我们尝试输出消息类型(就是MSG中的第二个参数)那么我们输出WindowProc中的uMSG即可。示例代码如下
#include "framework.h" #include "WindowsProject1.h" LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { char szOutBuff[0x80]; sprintf(szOutBuff, "消息类型: %x n", uMsg); OutputDebugStringA(szOutBuff); return DefWindowProc(hwnd, uMsg, wParam, lParam); } int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { char szOutBuff[0x80]; //1.第一步:定义你的窗口是什么样的 TCHAR className[] = TEXT("My First Window"); WNDCLASS wndclass = { 0 }; wndclass.hbrBackground = (HBRUSH)COLOR_BACKGROUND; wndclass.lpszClassName = className; wndclass.hInstance = hInstance; wndclass.lpfnWndProc = WindowProc;//不是调用函数,只是告诉函数名 *** 作系统会来调用 RegisterClass(&wndclass); //第二部:创建并显示窗口 HWND hwnd = CreateWindow( className, TEXT("我的第一个窗口"), WS_OVERLAPPEDWINDOW, 10, 10, 600, 300, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) { sprintf(szOutBuff, "Error:%d", GetLastError()); OutputDebugStringA(szOutBuff); return 0; } ShowWindow(hwnd, SW_SHOW); //3.第三步:接收消息并处理 MSG msg; BOOL bRet; while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { // handle the error and possibly exit } else { //转换消息 //TranslateMessage(&msg); DispatchMessage(&msg);//分发消息 } } return 0; }
我们通过Debugview++看到输出:
我们可以看到有很多消息(还有很多没有截图),不要认为只有鼠标或者键盘点的时候才有消息,我们的内核程序在执行的时候就已经有消息了。我们可以具体查看一下这些消息类型比如说上面的消息类型:1。我们在VS2019(我用的2019)中把MSG转到定义看一下他是数以一个
文件的,我们就在里面找
找到了,那么这个消息类型是什么意思呢?我们用官方文档查询。
WM_CREATE消息: 当应用程序请求通过调用CreateWindowEx或CreateWindow函数创建窗口时发送。(在函数返回之前发送消息。新窗口的窗口过程在创建窗口后,但在窗口变为可见之前接收此消息。 窗口通过其WindowProc函数接收此消息。
简单来说就是我们的窗口一创建的时候 *** 作系统产生一个消息,这个消息类型就是WM_CREATE.有很多消息类型,这些消息类型对应一个编码,当我们需要的时候我们可以利用,我们不需要的时候就可以不用理会。
比如:当窗口最大化的时候会有一个消息类型,如果我们想要在最大化 的时候做些什么就可以判断,如果产生这个值那么怎样怎样。
总而言之我们应该知道消息是时刻产生的,鼠标可以产生、键盘可以产生、别的程序可以产生等等。
不过有一些重要的消息类型我们应该去知道并加以利用。就比如我们在执行之前的代码的时候关掉窗口再次执行可能会报错。
可能大家之前写程序的时候也遇到过这个错误,原因就是你之前打开的程序并没有真正关闭,我们需要在任务管理器中终止程序才能进行。我们也发现当我们执行上面给的代码,关闭窗口后仍可以在任务管理器中找到这个程序。
所以只是窗口没了,程序还在跑。那么怎么解决呢?我们就可以利用消息类型捕捉这个关闭窗口的消息,判断是否产生这个消息再通过代码来终止这个程序。
我们知道每个消息类型都对应一种编号
这个2就对应关闭窗口。其实我们从英文意思就可以看出3是移动窗口,5是改变大小。前面WM就是Windows和Message的意思。
通常我们用switch语句来写,我们可以用PostQuitMessage()函数退出程序
void PostQuitMessage( [in] int nExitCode//填0 );
示例代码如下
#include "framework.h" #include "WindowsProject1.h" LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { switch (uMsg)//这里就是对消息类型的捕捉,如果是关闭窗口的话就会终止程序,如果是其他 *** 作的话没有影响 { case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); } int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { char szOutBuff[0x80]; //1.第一步:定义你的窗口是什么样的 TCHAR className[] = TEXT("My First Window"); WNDCLASS wndclass = { 0 }; wndclass.hbrBackground = (HBRUSH)COLOR_BACKGROUND; wndclass.lpszClassName = className; wndclass.hInstance = hInstance; wndclass.lpfnWndProc = WindowProc;//不是调用函数,只是告诉函数名 *** 作系统会来调用 RegisterClass(&wndclass); //第二部:创建并显示窗口 HWND hwnd = CreateWindow( className, TEXT("我的第一个窗口"), WS_OVERLAPPEDWINDOW, 10, 10, 600, 300, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) { sprintf(szOutBuff, "Error:%d", GetLastError()); OutputDebugStringA(szOutBuff); return 0; } ShowWindow(hwnd, SW_SHOW); //3.第三步:接收消息并处理 MSG msg; BOOL bRet; while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { // handle the error and possibly exit } else { //转换消息 //TranslateMessage(&msg); DispatchMessage(&msg);//分发消息 } } return 0; }
输出
当我们关闭窗口的时候我们可以在任务管理器中找一下发现找不到这个程序了,也就是说在我们关闭窗口的时候这个程序也终止了。
再比如我们想要接受一个键盘消息,比如当我们按下A的时候把A打印出来怎么做呢?
如上图,键盘按下去产生的消息就叫KEYDOWN,d起来就叫KEYUP。同理我们放入switch语句中
代码如下(其他地方不用修改只用修改WindowProc,所以其他代码就不复制了)
LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { switch (uMsg) { case WM_DESTROY: { PostQuitMessage(0); return 0; } case WM_KEYDOWN: { MessageBox(0,0,0,0);//先不设置,我们看下效果 return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
但我们按下键盘输出:
如图我们现在已经可以捕捉键盘按下的消息了。现在我们希望按下键盘知道按的是谁。我们查下官方文档
LRESULT CALLBACK WindowProc( HWND hwnd, WM_IME_KEYDOWN, WPARAM wParam, LPARAM lParam );
我们知道WindowProc()中的第二个参数MSG只是消息类型,它并不能知道我们按下的是哪个键,这时就要用到第三个和第四个参数了。
wParam 非系统键的虚拟键代码。
这个参数就是说每个键都对应一个虚拟码。我们把这个虚拟码打印出来(就改一部分):
LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { switch (uMsg) { case WM_DESTROY: { PostQuitMessage(0); return 0; } case WM_KEYDOWN: { char szOutBuff[0x80]; sprintf(szOutBuff, "消息: %x - %x n", uMsg, wParam);//第一个打印消息类型,第二个打印虚拟码 OutputDebugStringA(szOutBuff); return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
输出:
如图我们按下A键,第一个100指的是消息类型(KEYDOWN)第二个是A十六进制是41十进制是65也就是ASCII码对应的A。
我们再试试第四个参数,lParam:
这是什么意思呢,我们先打印出来看看。
LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { switch (uMsg) { case WM_DESTROY: { PostQuitMessage(0); return 0; } case WM_KEYDOWN: { char szOutBuff[0x80]; sprintf(szOutBuff, "消息: %x - %x -%x n", uMsg, wParam,lParam); OutputDebugStringA(szOutBuff); return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
输出:
其实这个就是一个32位数来表示各种信息,每个位数表示的信息就对应上面那张图。比如第30位就是如果你之前就是按着键盘的(我按住A了)那就是1如果没按就是0.比如这个401e0001拆开就是
0100 0000 0001 1110 0000 0000 0000 0001 左边第二个数就是第30位是1就说明是按着的。
这个200001
0000 0000 0010 0000 0000 0000 0000 0001 左边第二个数是0说明没按着
我们每个类型的消息都有wParam和lParam这两个参数,这两个参数的含义是由消息类型决定的,不同的消息类型不一样。
比如:这两个参数在KEYDOWN中有用,可是在DESTROY(关闭窗口)中没有。看下官方文档
可以看到不使用这两个参数。
接着上面的问题,我们想要按一个键就输出一个,虽然上面输出了对应的虚拟码但是还不够完美我们想要直接输出按的键怎么办呢?我们这时就不用KEYDOWN而是用CHAR这个用来接收键盘上的字符消息。看下官方文档
这个对应的就是字符了。示例代码
LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { switch (uMsg) { case WM_DESTROY: { PostQuitMessage(0); return 0; } case WM_CHAR: { char szOutBuff[0x80]; sprintf(szOutBuff, "消息: %c n", wParam);//这里打印 OutputDebugStringA(szOutBuff); return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
我们这样写发现我们按键盘并不会输出消息,因为这里我们还需要用到TranslateMessage()(之前我屏蔽了这个代码)
将虚拟键消息转换为字符消息。 BOOL TranslateMessage( [in] const MSG *lpMsg );
这个函数使用来转换消息的,如果不转换我们就得不到字符。它所干的就是把按键盘得到的虚拟码转换成字符,
示例代码如下:
#include "framework.h" #include "WindowsProject1.h" LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { switch (uMsg) { case WM_DESTROY: { PostQuitMessage(0); return 0; } case WM_CHAR: { char szOutBuff[0x80]; sprintf(szOutBuff, "消息: %c n", wParam); OutputDebugStringA(szOutBuff); return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); } int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { char szOutBuff[0x80]; //1.第一步:定义你的窗口是什么样的 TCHAR className[] = TEXT("My First Window"); WNDCLASS wndclass = { 0 }; wndclass.hbrBackground = (HBRUSH)COLOR_BACKGROUND; wndclass.lpszClassName = className; wndclass.hInstance = hInstance; wndclass.lpfnWndProc = WindowProc;//不是调用函数,只是告诉函数名 *** 作系统会来调用 RegisterClass(&wndclass); //第二部:创建并显示窗口 HWND hwnd = CreateWindow( className, TEXT("我的第一个窗口"), WS_OVERLAPPEDWINDOW, 10, 10, 600, 300, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) { sprintf(szOutBuff, "Error:%d", GetLastError()); OutputDebugStringA(szOutBuff); return 0; } ShowWindow(hwnd, SW_SHOW); //3.第三步:接收消息并处理 MSG msg; BOOL bRet; while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { // handle the error and possibly exit } else { // TranslateMessage(&msg); DispatchMessage(&msg);//分发消息 } } return 0; }
输出:
成功了
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)