深入浅出MFC -第一个win32程序
一、WinMain入口
我们在学习C/C++时,主程序的入口是main函数,从main函数跳出时程序就结束了。在windows编程里,也是一样的,不过不是入口函数不是main函数了,而是WinMain函数。WinMain函数是windows API提供好的,它的原型是
int CALLBACK WinMain(
_In_ HINSTANCE hInstance; // 应用程序当前实例句柄
_In_ HINSTANCE hPrevInstance; // 应用程序先前实例句柄
_In_ LPSTR LpCmdLine; // 指定应用程序命令行的字符串指针
_In_ int nCmdShow; // 指明窗口如何显示
);
当Windows执行一个程序时,先调用加载器把程序加载,然后调用C startup code,后者再调用WinMain,开始执行程序。WinMain的四个参数都是由操作系统传递进来的。
win32程序的执行流程,设计窗口类、注册窗口类、创建窗口、显示窗口、更新窗口、消息循环。
上面这张图展示了注册窗口类和创建窗口时,窗口界面、消息循环和窗口图片资源之间的关系。
二、设计与注册窗口类
设计窗口类就是设计我们的窗口,如 窗口标题 、背景颜色、边框和窗口大小等等。
设计窗口类我们要用到一个结构体
- typedef struct tagWNDCLASS {
- UINT style; // 窗口类样式
- WNDPROC lpfnWndProc; // 窗口消息处理函数
- int cbClsExtra; // 窗口类扩展
- int cbWndExtra; // 窗口实例扩展
- HINSTANCE hInstance; // 当前实例句柄
- HICON hIcon; // 窗口图标
- HCURSOR hCursor; // 窗口光标
- HBRUSH hbrBackground; // 窗口背景
- LPCTSTR lpszMenuName; // 窗口菜单
- LPCTSTR lpszClassName; // 窗口类名
- } WNDCLASS, *PWNDCLASS;
第一个成员是窗口类样式,注意不要和窗口样式WS_XXX 混淆了。这里指的是这个窗口类的特征,不是窗口的外观特征,这两个style是不一样的。通常我们只需要两个,CS_HREDRAW|CS_VREDRAW,代表窗口同时具备垂直重画和水平重画。因为当我们的窗口显示的时候,被其他的窗口挡住后重新显示,或者大小调整,窗口都要进行重绘,每次窗口发生变化都要重绘一次,并发送WM_PAINT消息。
第二个参数 lpfnWndProc 是一个函数指针,用来设置消息回调函数callback WinowsProc( ).callback 是用来定义回调函数的,WindowsProc函数名不是固定的,你可以自己定义函数名。
cbClsExtra和cdWinExtra通常不需要,设置为0就可以。
hInstance是当前应用程序的实例句柄,从WinMain的hInstance参数中可以得到。
lpszMenuName指的是菜单的ID,没有菜单就NULL,lpszClassName就是我们要向系统注册的类名,字符,不能与系统已存在的类名冲突,如“BUTTON”类。
实际使用中怎么设计窗口类:
WNDCLASS wndclass;
char lpszClassName[]="窗口"; //窗口类名
char lpszTitle[]="测试窗口"; //窗口标题名
//窗口类定义,窗口类定义了窗口的形式与功能,窗口类定义通过给窗口类数据结构WNDCLASS赋值完成
//该数据结构中包含窗口类的各种属性
wndclass.style =0; // 窗口类型为缺省类型CS_ Class Style
wndclass.lpfnWndProc=WndProc; //定义窗口处理函数
wndclass.cbClsExtra=0; //窗口类无扩展
wndclass.cbWndExtra=0; //窗口实例无扩展
wndclass.hInstance=hInstance; //当前实例句柄
wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION); //窗口的最小化图标为缺省图标
wndclass.hCursor=LoadCursor(NULL,IDC_ARROW); // 窗口采用箭头光标
wndclass.hbrBackground=(HBRUSH)(GetStockObject(WHITE_BRUSH)); //窗口背景为白色
wndclass.lpszMenuName=NULL; //窗口无菜单
wndclass.lpszClassName=lpszClassName; //窗口类名为“窗口”
设计完窗口类不要忘记注册:
Register(&wndclass);
三、创建和显示窗口
窗口类设计注册完成后,就应该创建窗口,显示窗口了,调用createWindow创建窗口,如果成功,会返回一个窗口的句柄。
- // 创建窗口
- HWND hwnd = CreateWindow(
- cls_Name, //类名,要和刚才注册的一致
- L"我的应用程序", //窗口标题文字
- WS_OVERLAPPEDWINDOW, //窗口外观样式
- 38, //窗口相对于父级的X坐标
- 20, //窗口相对于父级的Y坐标
- 480, //窗口的宽度
- 250, //窗口的高度
- NULL, //没有父窗口,为NULL
- NULL, //没有菜单,为NULL
- hInstance, //当前应用程序的实例句柄
- NULL); //没有附加数据,为NULL
- if(hwnd == NULL) //检查窗口是否创建成功
- return 0;
窗口创建完就,就要显示它,就像我们的产品做好了,要向客户展示。显示窗口调用ShwWindow函数。
// 显示窗口
ShowWindow(SW_SHOW);
四、更新窗口(可选)
为什么更新窗口这一步可有可无呢?因为只要程序在运行着,只要不是最小化,只要窗口是可见的,那么,我们的应用程序会不断接收到WM_PAINT通知。这里先不说,后面你会明白的。好了,更新窗口,当然是调用UpdateWindow函数。
// 更新窗口
UpdateWindow(hwnd);
五、消息循环
Windows操作系统是基于消息控制机制的,用户与系统之间的交互,程序与系统之间的交互,都是通过发送和接受消息完成的。
只要有与用户交互,系统就会不断的向应用程序发送消息,因为这些消息是不定时不断发送的,必须要有一个缓冲区,就好像排队打饭一样,我们从最前端一条一条取出消息处理,后面新发送的消息会一直排队,直到所有的消息都处理完成,这就是消息队列。
要取出一条消息,调用GetMessage函数。函数会传入一个MSG结构体指针,当收到消息,会填充MSG结构体中的成员变量,这样我们就知道我们的应用程序收到的什么消息,直到GetMessage函数取不到消息,条件不成立,循环跳出,这时应用程序就退出了。MSG定义如下:
- typedef struct tagMSG {
- HWND hwnd;
- UINT message;
- WPARAM wParam;
- LPARAM lParam;
- DWORD time;
- POINT pt;
- } MSG, *PMSG, *LPMSG;
成员变量含义:第一个成员变量hwnd表示消息所属的窗口;第二个成员变量message指定了消息的标识符。第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。
GetMessage函数声明如下:
- BOOL WINAPI GetMessage(
- _Out_ LPMSG lpMsg,
- _In_opt_ HWND hWnd,
- _In_ UINT wMsgFilterMin,
- _In_ UINT wMsgFilterMax
- );
第一个参数是以LP开头,它就是 MSG* ,一个指向MSG结构的指针。第二个参数是句柄,通常我们用NULL,因为我们会捕捉整个应用程序的消息。后面两个参数是用来过滤消息的,指定哪个范围内的消息我接收,在此范围之外的消息我拒收,如果不过滤就全设为0.。
MSG msg;
while(GetMessage(&msg,NULL,0,0))
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg); // 转换消息
DispatchMessage(&msg); // 分派消息
}
TranslateMessage(&Msg);//对"消息对"的转化,如对键盘的WM_KEYDOWN和WM_KEYUP消息对转化为WM_CHAR消息,并且将转换后的新消息投递到我们的消息队列中去,这个转化操作不会影响原来的消息,只会产生一个新的消息。
DispatchMessage(&Msg);//DispatchMessage()函数是将我们取出的消息传到窗口的回调函数去处理;可以理解为该函数将取出的消息路由给操作系统,然后操作系统去调用我们的窗口回调函数对这个消息进行处理。
六、响应消息
窗口响应函数定义了应用程序对接收到的不同消息的响应,其中包含了应用程序对各种可能接受到的消息的处理过程,时消息处理分支控制语句的集合
long CALLBACK WndProc(HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
PostQuitMessage(0);
default: //缺省时采用系统消息缺省处理函数
return DefWindowProc(hwnd,message,wParam,lParam);
}
return (0);
}
下面就是简单完整的win32程序代码:
#include <Windows.h>
// 必须要进行前导声明
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
// 程序入口点
int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow
)
{
// 类名
WCHAR* cls_Name = L"My Class";
// 设计窗口类
WNDCLASS wc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hCursor = LoadCursor(hInstance, IDC_ARROW);
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpfnWndProc = WindowProc;
wc.lpszClassName = cls_Name;
wc.hInstance = hInstance;
// 注册窗口类
RegisterClass(&wc);
// 创建窗口
HWND hwnd = CreateWindow(
cls_Name, //类名,要和刚才注册的一致
L"Win32窗口", //窗口标题文字
WS_OVERLAPPEDWINDOW, //窗口外观样式
38, //窗口相对于父级的X坐标
20, //窗口相对于父级的Y坐标
480, //窗口的宽度
250, //窗口的高度
NULL, //没有父窗口,为NULL
NULL, //没有菜单,为NULL
hInstance, //当前应用程序的实例句柄
NULL); //没有附加数据,为NULL
if(hwnd == NULL) //检查窗口是否创建成功
return 0;
// 显示窗口
ShowWindow(hwnd, SW_SHOW);
// 更新窗口
UpdateWindow(hwnd);
// 消息循环
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// 在WinMain后实现
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
switch(uMsg) // 处理对应的消息
{
case WM_KEYDOWN:// 键盘按键被按下消息
{
switch(wParam)
{
case VK_F5: // F5键
MessageBox(NULL, L"键盘F5按键按下",L"消息",MB_OK);
break;
}
}
break;
case WM_KEYUP: // 键盘按键弹起消息
{
switch(wParam)
{
case VK_F4: // F4键
MessageBox(NULL,L"F4键弹起",L"MSG",MB_OK);
break;
}
}
break;
case WM_DESTROY:
{
PostQuitMessage(0); // 给操作系统发送退出消息
return 0;
}
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam); // 将不需要处理的消息发送给系统进行默认处理
}