上次,我们一起写了一个Windows窗口程序,这个窗口程序虽然非常简单,但是,代码仍然很多,相信,一定会有很多初学者看见这些代码而感到头疼。不用怕,现在,我们就一起来分析一下这些代码,相信通过我们共同的努力,一定可以克服这些难题。
首先,我们要做的第一件事情就是包含windows.h头文件,这个不再解释了,因为,我们前面已经解释过。
之后,我们定义了一个WinMain函数和一个窗口处理函数MainWndProc。
相信,很多人读到窗口处理函数,就一定会发晕,不知道它是一个什么东东,如果大家想要明白窗口处理函数,就必须先明白Windows下的消息机制,现在,我先简要介绍一下什么是消息机制,如果想要深入研究的人,可以看看《Windows核心编程》,windows的消息机制是借由消息系统来完成的。
Windows的消息系统是由3个部分组成的:
1)消息队列:Windows为所有的应用程序都维护一个消息队列,应用程序必须从消息队列中获取消息,然后分派给某个窗口处理函数处理。
2)消息泵:通过这个消息泵,应用程序可以从消息队列中获取消息,然后,再把它分派给适当的窗口,最终由该窗口的窗口处理函数来处理它。
3)窗口处理函数:每个窗口都有一个窗口处理函数,它的任务就是处理发给该窗口的消息,处理完消息后,它通常要返回一个值给Windows,告诉Windows该消息处理是否成功。
相信通过上面的了解,大家大概对消息机制会有一个整体的把握,下面,我将以一个键盘的按键消息为例,告诉大家这个消息是如何产生,流转,处理。
当我们按下键盘上的一个字符后,比如a,这个时候,键盘的硬件会检测到这个动作,然后通知系统,系统知道后,会为它产生一个消息:WM_CHAR,之后,系统会将这个消息发送到当前程序的消息队列,比如当前程序是记事本,那么这个时候,记事本程序的消息队列中会出现一个WM_CHAR消息,当记事本程序的消息泵从消息队列提取到这个字符的按键消息后,会调用当前窗口的处理函数来处理这个字符消息,当窗口处理函数处理WM_CHAR时,会在屏幕上显示这个字符,我们这里,这个字符是a,这就是整个消息处理的全过程。
好了,现在,我们言归正传,继续分析接下来的程序。WinMain程序,这个是所有Windows程序的入口点,它是必须要有的,上次,我们已经讲过,这里不再提及。
接下来,我们来一起看一下这个WinMain函数都做了什么?
为了帮助大家理解WinMain函数,我们这里先使用自然语言进行概括性的描述。
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
1、定义一个WNDCLASS或者WNDCLASSEX变量wcx;
2、设置WNDCLASSEX变量wcx;
3、利用wcx创建注册一个窗口类MainWClass,窗口类就好比一个模版,Windows系统会参考这个模版,为我们创建窗口;
4、用我们创建的窗口类MainWClass,创建一个窗口;
5、调用ShowWindow和UpdateWindow显示和更新窗口;
6、创建一个消息泵,这个消息泵的任务就是不断从消息队列中提取消息,并将消息传递给当前窗口的处理函数,
当消息泵接收到WM_QUIT退出消息后,消息泵就会退出;
7、程序返回。
}
好了,现在,我们继续分析WinMain函数的内容:
1)定义一个WNDCLASS或者WNDCLASSEX变量wcx,这个目标是通过如下的代码来完成的:
WNDCLASSEX wcx;
2)设置窗口类,具体实现是通过如下的代码:
wcx.cbSize = sizeof(wcx); // 这个参数设置wcx的大小;
wcx.style = CS_HREDRAW |CS_VREDRAW; // 告诉系统,当窗口大小改变时,重绘整个窗口;
wcx.lpfnWndProc = MainWndProc; // 告诉系统,当前窗口类的消息处理函数是MainWndProc;
wcx.cbClsExtra = 0; // 这个大家先不用管,设置为0就可以;
wcx.cbWndExtra = 0; // 这个大家也先不用管,设置为0就可以;
wcx.hInstance = hinstance; // 告诉系统当前这个窗口属于哪个程序,系统会根据这个参数,将该窗口的消息发送到这个程序的消息队列;
wcx.hIcon = LoadIcon(NULL,IDI_APPLICATION); // 窗口图标:大家也先不用管,使用这个IDI_APPLICATION值,就可以;
wcx.hCursor = LoadCursor(NULL,IDC_ARROW); //设置这个窗口的光标样式,大家也先不用管,设置为IDC_ARROW,就可以;
wcx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 设置当前窗口的背景颜色,这里是白色WHITE_BRUSH;
wcx.lpszMenuName = NULL; //当前窗口的菜单,我们这个窗口没有菜单,所以,只需要设置为NULL,就可以;
wcx.lpszClassName = "MainWClass"; // 告诉系统,我们这个窗口的类名;
wcx.hIconSm = (HICON)LoadImage(hinstance, // 当前窗口的小图标,大家先这么写,先不用管具体什么意思,后面会介绍。
MAKEINTRESOURCE(5),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_DEFAULTCOLOR);
3)利用wcx创建注册一个窗口类MainWClass,具体通过如下的代码来完成:
if (!RegisterClassEx(&wcx))
{
return 1;
}
4)用我们创建的窗口类MainWClass,创建一个窗口,具体通过如下代码来完成:
hwnd = CreateWindow(
"MainWClass", // 窗口类名
"我们的第二个程序", // 窗口标题
WS_OVERLAPPEDWINDOW, // 窗口样式
CW_USEDEFAULT, // 水平位置X:默认
CW_USEDEFAULT, // 垂直位置Y:默认
CW_USEDEFAULT, // 宽度:默认
CW_USEDEFAULT, // 高度:默认
(HWND)NULL, // 父窗口:无
(HMENU)NULL, // 菜单:使用窗口类的菜单
hinstance, // 应用程序实例句柄
(LPVOID)NULL); // 窗口创建时数据:无
5)调用ShowWindow和UpdateWindow显示和更新窗口,具体通过如下代码来完成:
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
6)创建一个消息泵,具体通过如下的代码来完成:
while ((fGotMessage = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 && fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
7)程序返回,具体通过如下的代码来完成:
return msg.wParam; //这个msg通常是WM_QUIT,这个wParam是这个消息的退出码,通常由PostQuitMessage来设置,主要是通知系统,该程序是否正常退出。
相信,通过上面这段描述,大家一定对这个例子中的WinMain函数有了一个整体的把握,现在,我们趁热打铁,再一起分析一下我们这个窗口类的窗口处理函数。
现在,我先告诉大家窗口处理函数的格式,具体如下:
LRESULT CALLBACK 窗口处理函数名(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
大家只需要记住,窗口处理函数的格式必须这么写,只有函数名可以改变,其它的都不可以改变,这个是系统规定的,记住就可以,其中WPARAM和LPARAM是Windows消息中的两个参数,当窗口处理函数被调用的时候,hwnd代表了当前的窗口,uMsg代表了当前的消息,在Windows中,每个消息都用一个整数来表示。wParam和lParam是这个消息中的两个参数。
下面,我先给出消息处理函数的一般流程:
LRESULT CALLBACK 窗口消息处理函数名(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMsg)
{
case 消息1:
{
消息处理函数1;
break;
}
case 消息2:
{
消息处理函数2;
break;
}
...
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
上面描述了消息处理函数的一般流程,我们按照这个一般流程来讲解一下我们的窗口处理函数,具体如下:
我们的窗口处理函数,只响应了一个消息WM_DESTROY,这个消息是在我们点击窗口的关闭按钮是产生的,当我们的消息泵接收到这个消息后,就会将它发给我们的窗口处理函数,然后,我们的窗口处理函数调用函数ExitThread,其中传递的是线程的退出码,线程正常退出,通常都设置为0,当执行完这个函数后,整个线程会结束运行,我们这里的线程只有一个,就是WinMian所在的线程,当这个线程退出后,整个程序就会结束运行。
对于其它的消息,我们都使用系统提供的默认消息处理函数来处理,这个函数的原型如下:
LRESULT DefWindowProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);
在这个函数中,hWnd就是当前窗口的句柄,Msg就是当前要处理的消息,wParam和lParam就是当前消息中的两个参数。
好了,今天讲了很多,相信,大家读完后,应该会对我们的第2个Windows程序有所理解,希望大家多实践,如果有暂时不明白的内容,可以先放下,因为主要的内容,我已经告诉你们了,次要的后边会根据需要讲解。