SDK编写Windows程序需完成3个框架模块:
<1>填空赋值WNDCLASS定义窗口类并注册;
<2>创建窗口,显示、刷新窗口;
<3>消息循环,编写消息处理函数。
熟悉框架后,我们的重点工作放在感兴趣的消息处理上。
有以上分析可分模块完成注册窗口类和创建窗口工作,即分别设计功能函数InitWindowsClass和InitWindows,改造后的完整程序如下,从以下程序可以更清楚的认识windows程序框架。
Windows窗口应用程序与DOS控制台应用程序是不同的,编写一个基于Windows API的典型的应用程序需要编写处理以下四个任务:
l 初始化
l 实例化
l 启动消息循环
l 响应消息
前面三个任务总是发生在WinMain函数中,WinMain是每一个Windows程序的入口点,相当于C/C++中的main。第四个任务是传统上称为WndProc的函数来承担的。
#include <windows.h> BOOL InitWindowsClass(HINSTANCE hInstance); // 注册窗口类 BOOL InitWindows(HINSTANCE hInstance, int nCmdShow); // 创建窗口 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // 窗口函数声明 HWND hMainWnd; // 全局实例窗口句柄 LPCTSTR lpszProviderClass = __TEXT(“MyWndClass”); // 应用程序入口函数(此程序第一个被执行) int WINAPI WinMain( HINSTANCE hInstance, // 程序实例句柄 HINSTANCE hPrevInstance, // 为保持与Win16兼容的句柄 LPSTR lpCmdLine, // 命令行参数 int nCmdShow // 初始化窗口显示方式 ) { MSG Msg; // 注册窗口类 if(!InitWindowsClass(hInstance)) // 注意这里的实例句柄hInstance { return FALSE; } if(!InitWindows(hInstance,nCmdShow)) // 注意这里的实例句柄hInstance { return FALSE; } // 消息循环 while(GetMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg); // 翻译消息 DispatchMessage(&Msg); // 将消息传递给处理函数 } return Msg.wParam; // 返回消息的附加参数 } // 消息处理函数,Windows系统规定每个消息处理函数的定义形式都相同 LRESULT CALLBACK WndProc( HWND hWnd, // 窗口句柄 UINT nMessage, // 接受到的待处理的消息标识 WPARAM wParam, // 消息附参数 LPARAM lParam // 消息附参数 ) { switch(nMessage) { // 响应WM_CREATE消息 case WM_CREATE: MessageBox(hWnd, __TEXT(“收到WM_CREATE消息!”), __TEXT(“通知”), MB_OK); break; //响应WM_LBUTTONDOWN消息 case WM_LBUTTONDOWN: MessageBox(hWnd, __TEXT(“收到WM_LBUTTONDOWN消息!”), __TEXT(“通知”), MB_OK); break; // 响应WM_CHAR消息 case WM_CHAR: TCHAR buffer[30]; wsprintf(buffer, __TEXT(“你刚才按下了%c键!”), wParam); MessageBox(hWnd, buffer, __TEXT(“通知”), MB_OK); break; // 响应WM_DESTROY消息 case WM_DESTROY: PostQuitMessage(0); break; // 必须调用函数DefWindowProc(),这是Windows系统多规定的 default: return DefWindowProc(hWnd,nMessage,wParam,lParam); } return FALSE; } // 注册窗口类 BOOL InitWindowsClass(HINSTANCE hInstance) { WNDCLASS WndClass; // 窗口类结构体 // 窗口类的定义 WndClass.style = CS_HREDRAW|CS_VREDRAW; // 窗口类型 WndClass.lpfnWndProc = (WNDPROC)WndProc; // 窗口处理函数为WndProc() WndClass.cbClsExtra = NULL; // 窗口类无扩展 WndClass.cbWndExtra = NULL; // 窗口实例无扩展 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 = lpszProviderClass; // 窗口所属类名 return RegisterClass(&WndClass); } // 创建窗口 BOOL InitWindows(HINSTANCE hInstance, int nCmdShow) { // 创建窗口 HWND hWnd; hWnd = CreateWindow( lpszProviderClass, // 注册的窗口类名 __TEXT(“Hello Windows!”), // 窗口标题名 WS_OVERLAPPEDWINDOW, // 窗口的风格 CW_USEDEFAULT, // 显示窗口的左上角的X坐标,取默认值 CW_USEDEFAULT, // 显示窗口的左上角的Y坐标,取默认值 CW_USEDEFAULT, // 显示窗口的右下角的X坐标,取默认值 CW_USEDEFAULT, // 显示窗口的右下角的Y坐标,取默认值 NULL, // 此窗口无父窗口 NULL, // 菜单句柄(此处设为没有菜单句柄) hInstance, // 程序实力句柄 NULL // 指向一个传递给窗口的指针型参数,此处设置为空 ); if(!hWnd) // 创建窗口失败,返回FALSE { return FALSE; } hMainWnd = hWnd; // 将创建的窗口句柄赋值给全局窗口句柄 ShowWindow(hWnd, nCmdShow); // 显示窗口 UpdateWindow(hWnd); // 刷新窗口 return TRUE; }
1.初始化(注册窗口类,创建窗口)
要定义一个窗口类struct WNDCLASS WndClass;该数据结构实际存储的是一个窗口的属性集。调用ATOM RegisterClass(CONST WNDCLASSW *lpWndClass);函数注册lpWndClass指向的窗口类模板。窗口类是一个内核对象,不同的窗口类以名称(WNDCLASS的lpszClassName字段)区分。
例如后面MFC中的AfxRegisterClass函数中,注册窗口类前先调用GetClassInfo函数检测预注册的窗口类是否已注册。GetClassInfo函数用来获取指定名称的窗口类信息,其原型如下:
BOOL GetClassInfo(HINSTANCE hInstance, LPCTSTR lpClassName, LPWNDCLASSW lpWndClass);
以下为AfxRegisterClass函数代码片段:
// WINCORE.CPP
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)
{
WNDCLASS wndcls;
if (GetClassInfo(lpWndClass->hInstance, lpWndClass->lpszClassName, &wndcls))
{
// class already registered
return TRUE;
}
if (!::RegisterClass(lpWndClass))
{
TRACE1(“Can’t register window class named %s/n”,
lpWndClass->lpszClassName);
return FALSE;
}
//……
}
对于一个已注册的窗口类,可调用BOOL UnregisterClass(LPCTSTR lpClassName, HINSTANCE hInstance);函数进行注销。
以下为Win32SDK示例程序中的注册窗口类、创建窗口的代码片段:
lpszProviderClass = __TEXT(“MyWndClass“);
WndClass.lpszClassName = lpszProviderClass;
然后给定义窗口风格以及用于该窗口类的消息处理函数。然后CreateWindow(lpszProviderClass,……)将使用名称为lpszProviderClass的窗口类WndClass作为模版创建类的实例。
在调用CreateWindow(Ex)时,第一个参数也可以直接使用系统预定义(Predefined)的子窗口控件类,例如“BUTTON”,“EDIT”,“COMBOBOX”等这些是系统内部已注册的WNDCLASS,免去调用RegisterClass。
创建窗口后,调用GetClassName函数获取指定窗口hWnd所使用的窗口类名称。其函数原型如下:
int GetClassName(HWND hWnd, LPTSTR lpClassName, int nMaxCount);
2.实例化(显示窗口,刷新窗口)
一旦窗口被创建,还需要完成若干步才能运行你的程序。首先,当一个窗口被创建时,它通常是不可见的,一般要调用BOOL ShowWindow(HWND hWnd, int nCmdShow);函数来显示窗口,调用BOOL UpdateWindow(HWND hWnd); 函数来刷新窗口。
函数UpdateWindow向窗口hWnd发送第一个WM_PAINT消息以更新它的客户区。当ShowWindow使窗口显示在屏幕上时,窗口的客户区会被WndClass.hbrBackground擦除,调用UpdateWindow函数将促使客户区重绘,以显示其内容。
3.消息循环
(1)消息的产生
无论用户移动鼠标或按下某个键,Windows系统将创建一个消息(对应数据结构为MSG)并将之投放到对应窗口(MSG.hwnd指标)所在线程的消息队列中。
(2)获取消息
应用程序调用GetMessage函数从调用线程消息队列中取出(pop)消息。
GetMessage函数原型如下:
BOOL GetMessage(LPMSG lpMsg, HWND hWnd , UINT wMsgFilterMin, UINT wMsgFilterMax);
取出的消息存放至参数一lpMsg所指内存(本地声明的MSG结构体变量)。
参数二hWnd标识要检查消息的窗口,如果为NULL,则获取属于调用线程消息队列中任一(窗口)消息;如果不为NULL,则只获取指定窗口的消息。
wMsgFilterMin和wMsgFilterMax指定获取消息码的范围。一般将两参数都置零,表示无消息ID范围限制。
注意该函数是阻塞的,直到有一个(hWnd的)消息到来,它才返回。如果消息为WM_QUIT,则该函数返回FALSE,结束消息循环;否则返回TRUE,继续下一轮消息循环。
<1>等待消息
函数BOOL WaitMessage(VOID);使调用线程挂起,直到一个新的消息放到调用线程消息队列中才返回。
<2>取出消息
函数PeekMessage查看调用线程消息队列中(指定窗口hWnd)是否有消息。如果有消息则取出放入lpMsg所指MSG结构体中,返回TRUE;如果无(指定窗口hWnd的)消息则立即退出(即非阻塞),返回FALSE。其函数原型如下:
BOOL PeekMessage(LPMSG lpMsg, HWND hWnd , UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg);
前四个参数同GetMessage,wRemoveMsg参数指定当有消息读取消息后,是否从消息队列中移除该消息。其为下列值之一:
PM_NOREMOVE: 读取后消息依然留在队列中;
PM_REMOVE: 读取后消息从队列中移除;
<3>GetMessage = WaitMessage + PeekMessage(PM_REMOVE)。
(3)翻译消息
如果输入列表中有一个属于你的应用程序的消息,并且是按键消息时,TranslateMessage将虚拟键消息转换为字符消息(WM_KEYDOWN + WM_KEYUP = WM_CHAR或WM_SYSKEYDOWN + WM_SYSKEYUP = WM_SYSCHAR),再将字符消息(WM_CHAR或WM_SYSCHAR)投递到调用线程的消息队列中。
TranslateMessage函数内部机制大致如下:
BOOL TranslateMessage(CONST MSG *lpMsg)
{
if (lpMsg->message == WM_KEYDOWN || lpMsg->message == WM_SYSKEYDOWN)
{
UINT message;
if (lpMsg->message == WM_KEYDOWN)
{
message = WM_CHAR;
}
else if (lpMsg->message == WM_SYSKEYDOWN)
{
message = WM_SYSCHAR;
}
PostMessage(lpMsg->hwnd, message, lpMsg->wParam, lpMsg->lParam);
return TRUE; // 消息被转换
}
return FALSE;
}
(4)路由消息
取出消息后,需要进行处理。应用程序调用DispatchMessage函数将取得的消息发送到窗口消息处理函数WndProc。WndProc采用switch-case分支结构对不同的消息不同的响应处理。
WndProc为WNDPROC函数指针,其类型如下:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
DispatchMessage函数内部机制大致如下:
LRESULT DispatchMessage(CONST MSG *lpMsg)
{
// 根据lpMsg->hwnd查找用于创建该窗口的WNDCLASS(CreateWindow的lpClassName指定)的lpfnWndProc;
CallWindowProc(lpfnWndProc, lpMsg->hwnd, lpMsg->message, lpMsg->wParam, lpMsg->lParam);
}
4.消息处理
Windows编程中常见的消息有:窗口创建消息WM_CREATE,窗口绘制消息WM_PAINT,按键消息WM_KEYDOWN, WM_CHAR, 窗口关闭消息WM_CLOSE,窗口销毁消息WM_DESTROY,退出应用程序消息WM_QUIT等。
默认情况下,关闭窗口的处理流程如下:
点击关闭按钮à系统向指定窗口发送WM_CLOSE消息àWM_CLOSE消息响应中调用DestroyWindow向窗口发送WM_DESTROY消息àWM_DESTROY消息响应中调用PostQuitMessage向窗口发送WM_QUIT消息结束窗口所属线程的消息循环(GetMessage函数返回),终止窗口所属线程。
实际应用软件在响应关闭消息时通常将窗口最小化(到托盘),并不真正关闭。应用程序将在没有窗口的条件下继续运行。
参考:窗口破坏过程与Windows消息循环、WM_CLOSE、WM_DESTROY和WM_QUIT三者的区别。
每个Windows程序(进程)都至少包含一个窗口和一个应用程序的实例,在程序中表现为窗口句柄hWnd和实例句柄hInstance. 我们可以透过以下几条程序语句一窥Windows程序运行机制:
WndClass.hInstance=hInstance;//这一赋值表明要注册的窗口类属于当前实例
ShowWindow(hWnd,nCmdShow); UpdateWindow(hWnd); //显示刷新该程序窗口
GetMessage //提取消息队列中的消息
TranslateMessage(&Msg); //翻译消息,该函数负责将消息的虚拟键转换成字符消息
DispatchMessage(&Msg); //将参数lpMSG标识的消息发送给窗口函数WndProc
switch(nMessage) case: //按接受到的消息值nMessage判断是哪一种消息并处理
以下为Windows应用程序执行流程图: