如果把操作系统比喻成一个生产汽车的公司;应用程序在内存的那块空间比喻成为一个汽车工厂的厂房;窗口类WNDCLASS比喻成为图纸(或生产汽车用到的部件的清单);窗口Window比喻成为具体的汽车(如奥迪A6)。则整个生产汽车的过程(建立窗体的过程)如下图:
需要注意的难点是:如果注册多个窗口类并且每个窗口类都对应一个自己的窗口函数,则相当于注册了多个窗口函数(全局的函数)到操作系统,那么while消息循环中DispatchMessage(&msg); 这一句话会根据hWnd参数即窗口句柄把对应的msg发送都对应的窗口函数(至于怎么对应的,应该是hWnd找到那个窗口对象,而每个窗口对象又都是根据某个注册的窗口类来创建的,那么每个窗口对象里面应该保存有对应窗口类的名称,也就是上面图中清单的名称,所以更具hWnd就能找到那个窗口类即清单,找到了清单自然就能找到对应的窗口函数了),所以hWnd参数是很重要的。关于这个问题我已经测试过了。
总之:可以简单的认为hWnd参数和某个窗口函数对应或关联,hWnd确定了,则DispatchMessage函数就能够转向某个窗口函数了。
下面解释新建一个空的Win32应用程序书写代码的步骤:
首先,exe应用程序由操作系统调用进入内存,再给应用程序分配好内存后,操作系统给这个应用程序一个HINSTANCE句柄,然后操作系统调用应用程序的winmain函数开始执行。
winmain函数声明如下:
#include <windows.h> //windows用到的相关头文件,编写win32程序必须包含这个头文件
#include<stdio.h> //C语言的库函数所在的头文件,可能用到C语言的库函数,所以要包含进去
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
这个winmain函数是由操作系统调用的,所以里面的参数也都是由操作系统赋值的。其中hInstance指标志这个应用程序的句柄;hPrevInstance标志应用程序前一个应用程序的句柄,在win32下一般规定就是0;lpCmdLine是用户执行应用程序时传的命令行参数,比如dos下执行1.txt,则这个1.txt就是命令行参数,根据这个参数打开或新建对应的文件;nCmdShow指程序运行的主窗口显示的状态,是最大化、最小化还是隐藏显示。
第二,注册窗口类和创建具体的窗口
如果把产生一个窗体的过程比喻成生产一辆汽车,则生产这辆汽车需要地盘、轮子等部件,还可能需要汽车工程师等人员来装配。而窗口类WNDCLASS就是产生这辆汽车需要所有物件的清单,它描述了需要哪些部件才能生产出这辆汽车(也就是产生这个窗口)。在这个清单中,最重要的部件就是窗口函数的指针和应用程序实例句柄。
(1)窗口类的定制和注册,就是把清单中需要的具体部件都拿过来(或都产生出来,包括图标、光标、画刷等资源,这些部件可以被共享的,节约了内存)。
下面是把注册窗口类的所有过程封装在了一个函数MyRegisterClass中:
void MyRegisterClass(HINSTANCE hInstance) {
WNDCLASS wc; //定义一个窗口类的实例,即一个空清单
wc.style = CS_HREDRAW | CS_VREDRAW; //窗口类的样式,都是全局的宏定义,定义在WinUser.h中,这个样式是指较全局话的样式,比如是豪华车型还是普通车型,而不是指具体的车型如奥迪A6
wc.lpfnWndProc = WndProc; //窗口函数的首地址记录下来
wc.cbClsExtra = 0; //给这个窗口类的实例分配多大的扩展或保留内存,一般为0
wc.cbWndExtra = 0; //给以后生成的每个具体窗口的实例分配多大的扩展或保留内存,一般为0
wc.hInstance = hInstance; //本窗口对应的应用程序的句柄是多少
wc.hIcon = LoadIcon(NULL/*或hInstance*/, IDI_APPLICATION); //图标资源,其中hInstance是winmain中传来的句柄,为null时表示使用默认的标志图标,IDI_APPLICATION是全局的宏定义
wc.hCursor = LoadCursor(NULL, IDC_ARROW); //同上
wc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH); //背景画刷,DKGRAY_BRUSH是全局宏定义
wc.lpszMenuName = NULL; //菜单名,NULL表示不用菜单
wc.lpszClassName = "某个窗口类"; //这个窗口类实例的名字,标记这个窗口类的实例
RegisterClass(&wc); //窗口类实例的注册
}
窗口类实例的注册是在一个专门的内存区域进行备案,以后随时根据窗口类实例的名字挑选出来使用,用如下图可以便于理解:
(2)窗体的创建
HWND CreateWindow(
LPCTSTR lpClassName, //安装清单的名称,即窗口类实例的名称
LPCTSTR lpWindowName, //窗口实例的标题
DWORD dwStyle, //窗口实例的样式,具体的样式,具体的车型,比如奥迪A6
int x, //窗口左上角坐标值x
int y, //窗口左上角坐标值y
int nWidth, //窗口的宽度
int nHeight, //窗口的高度
HWND hWndParent, //父窗口的句柄
HMENU hMenu, //主菜单的句柄
HANDLE hInstance, //应用程序实例句柄,和窗口类实例一样都有
LPVOID lpParam //WM_Create消息用到的附加参数,一般只在mfc中用
);
(3)窗口的显示
bool ShowWindow(
HWND hwnd, //窗口实例的句柄
int nCmdShow //窗口的显示方式,最大化等
);
bool UpdateWindow(
HWND hWnd //窗口实例的句柄
);
第三、消息循环
消息循环是属于整个应用程序的(所以放在main函数中),因为消息循环要从消息队列中取消息,而消息队列是属于整个应用程序的。
消息结构的定义如下:
typedef struct tagMSG{
HWND hwnd; //产生消息的那个窗口实例的句柄
UINT message; //消息的类型(即事件的类型)
WPARAM wParam; //消息的附加信息1,比如存放WM_CHAR消息时的对应按键的ASCII码
LPARAM lParam; //消息的附加信息2,具体功能还不知道
DWORD time; //消息被投递到应用程序队列的时间
POINT pt; //当消息被投递时,光标在屏幕上的位置
}MSG;
消息循环的代码如下,在main函数中:
while(GetMessage(&,NULL,NULL,Null)){ //GetMessage函数只有收到WM_QUIT消息时才返回false
TranslateMessage(&msg); //对消息进行转换,如解释键盘消息,即当键盘按下时是WM_KeyDown消息,这个函数转换产生一个WM_CHAR消息并放入到应用程序消息队列中
DispatchMessage(&msg); //把消息路由给(发送给)操作系统,最后由操作系统负责调用具体的消息处理函数(窗口函数)
//我猜测分发消息时,是根据msg中的产生消息的那个窗口实例的句柄找到对应的窗口类的实例(清单),再根据这个窗口类实例(清单)找到具体的窗口函数
}
下面讲讲GetMessage函数
bool GetMessage(
LPMSG lpMsg, //装具体的消息
HWND hWnd, //窗口句柄,指示我要获取那个窗口的消息并装入到第一个参数lpMsg中,为NULL表示我要获取所有窗口的消息
UINT wMsgFilterMin, //消息范围的最小值,表示我要获取比这个值大的消息然后装入lpMsg中
Uint wMsgFilterMax //消息范围的最大值,如果最小和最大范围都设为0则表示不设置范围,获取所有范围的消息。
);
第四、窗口过程(函数)
LRESULT CALLBACK WndProc_XXX( //LRESULT是个LONG型,CALLBACK是_stdcall调用约定。 下面的四个参数其实就是消息结构体MSG的前四个参数(去掉了时间和光标当前位置)
HWND hwnd, //窗口句柄,由操作系统赋值
UINT uMsg, //消息类型,由操作系统赋值
WPARAM wParam, //消息附加参数,由操作系统赋值
LPARAM lParam //同上
)
{
HDC hdc;
PAINTSTRUCT ps;
switch (uMsg)
{
case WM_PAINT: //窗体重绘消息。如果在这里设置一个断点,则由于窗口从无到有都执行到这里,所以窗口就永无法显示出来
hdc=BeginPaint(hwnd,&ps); //所有的DC都是和窗口相关的
TextOut(hdc,0,0,"初始显示的文字",strlen("初始显示的文字"));
EndPaint(hwnd,&ps); //和BeginPaint是一对。另外BeginPaint函数只能用在WM_PAINT窗口重绘消息中,不能用在其他消息中
break;
case WM_CHAR:
char szChar[20];
spintf(szChar,"char is %d",wParam); //格式化字符串到内存szChar中
MessageBox(hwnd,szChar,"标题",MB_OK);
break;
case WM_LBUTTONDOWN:
MessageBox(hwnd,"您按下了鼠标左键","标题",MB_OK);
HDC hDC; //看作是一个和具体窗口关联了的画布
hDC=GetDC(hwnd);
TextOut(hDC,0,50,"画出了一些文字",strlen("画出了一些文字")); //在画布hDC上画文字
ReleaseDC(hwnd,hDC); //和GetDC是一对。另外GetDC函数只能用在其他消息中,不能用在WM_PAINT窗口重绘消息中
break;
case WM_CLOSE:
if(IDYES==MessageBox(hwnd,"是否退出程序?","标题",MB_YESNO));
{
DestroyWindow(hwnd); //这个函数会销毁窗口并发送一个WM_DESTROY和WM_NCDESTROY消息,从而转到下面的case语句
}
break;
case WM_DESTROY:
PostQuitMessage(0); //向消息队列中投递一个WM_QUIT消息,从而是while循环退出。参数0是作为WM_QUIT消息的附加参数,意义不大
break;
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam); //对其他的喜爱系进行缺省处理,不可缺少,否则窗口可能不显示出来。
}
return 0;
}