Windows窗口开发原理(窗口的创建&消息机制)
在windows应用程序中,窗口是其非常重要的一个元素。并且窗口是通过窗口句柄来标识的。句柄(HANDLE)是windows程序中一个重要的概念,其标识各种资源,包括图标句柄(HICON)、光标句柄(HCURSOR)和画刷句柄(HBRUSH)。
下面以一个带有自定义的画刷、光标和图标的windows窗口为例,讲解win32窗口的创建过程。
windows消息机制
windows程序是基于事件驱动方式的程序设计模式,主要是基于消息的。比如当用户在窗口中画图的时候,按下鼠标左键,此时os会感知到这一事件,于是将此事件包装成一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息,经过transltor翻译、分发消息,然后交由os调用
窗口过程函数(应用程序注册的回调函数)或(DefWindowProc系统默认的回调处理函数)进行处理。
创建一个窗口的基本流程
设计窗口类
窗口的特征是由WNDCLASS结构体来定义的,其定义了这个窗口的基本属性,所以我们只需填充结构体各成员信息即可。
typedef struct tagWNDCLASSW { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCWSTR lpszMenuName; LPCWSTR lpszClassName; } WNDCLASSW typedef WNDCLASSW WNDCLASS;//WNDCLASS是WNDCLASSW的别名
这里要首先说明下各类型定义
//LRESULT :long // UINT:unsigned int //WPARAM:unsigned int //LPARAM:unsigned int //LPCWSTR:const w_chart_t *; // typedef WORD ATOM; //BUGBUG - might want to remove this from minwin // typedef unsigned short WORD; // typedef unsigned long DWORD; //LPCWSTR const w_char_t * 宽字符 //LPCSTR const char *
wchar_t szAppclassName[] = _T("FirstWin32"); WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;//窗口类的样式(要让窗口在水平和垂直尺寸发生变化时发生重绘,我们可以使用用位或|操作符将其组合起来) //WNDPROC 函数指针类型 wc.lpfnWndProc = WindowProc;//窗口回调函数/窗口处理函数 wc.cbClsExtra = 0;//窗口类的附加内存大小 wc.cbWndExtra = 0;//窗口附加内存大小 wc.hInstance = hInstance;//当前应用程序实例句柄 wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));//加载自定义的图标句柄 //wc.hCursor = LoadCursor(NULL,IDC_CROSS);//光标句柄;加载系统光标,也可以采用下面的方式加载自定义的光标 wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1)); wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 0));//红绿蓝三原色0~255,[0最暗,255最亮] wc.lpszMenuName = NULL;//菜单名 wc.lpszClassName = szAppclassName;//窗口类型名 spy++(vs->工具选项)
其中的第二个成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数时一个回调函数。该函数签名如下:
WNDPROC lpfnWndProc;
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
故窗口函数按照如下签名定义,实现如下:
//窗口处理函数 //第一个参数:当前窗口句柄 //第二个参数:消息类型 //第三个参数:附加消息、附加操作 //第四个参数:附加消息、附加操作 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CLOSE://窗口关闭消息 DestroyWindow(hWnd);//销毁窗口,干掉界面,不会发出WM_QUIT,会发出一个WM_DESTORY消息 break; case WM_QUIT://窗口销毁消息 PostQuitMessage(0);//发布WM_QUIT break; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }
回调函数机制:
注册窗口类
通过ATOM WINAPI RegisterClass( _In_ CONST WNDCLASS*lpWndClass);进行窗口类注册。形参为WNDCLASS地址。返回值为ATOM.
创建窗口
创建窗口通过CreateWindow函数来实现,该函数API信息如下:
void CreateWindow( lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam );
关于返回值:
类型:HWND
如果函数成功,则返回值是新窗口的句柄。
如果函数失败,则返回值为NULL。要获取扩展错误信息,请调用GetLastError。
//WS:window style HWND hWnd = CreateWindow(szAppclassName, //窗口类型名 _T("这个是我的第一个windows应用程序") //窗口左上角标题 , WS_BORDER | WS_CAPTION | WS_MAXIMIZEBOX | WS_SYSMENU, //窗口的风格 200, 300, //窗口左上角坐标 800, 600, // //窗口宽和高 NULL, //父窗口句柄 NULL, //菜单句柄 hInstance, //应用程序实例句柄 NULL); //创建窗口的附加参数,WM_CREATE消息,lparam来接受这个参数 if (NULL == hWnd) { MessageBox(NULL, _T("创建窗口失败"), _T("提示"), MB_OK); }
显示窗口&更新窗口
创建完窗口后,要对窗口进行具体的显示。
显示函数API如下:
BOOL ShowWindow( HWND hWnd,//创建窗口后返回的窗口句柄 int nCmdShow //指定窗口显示的状态,包括SW_MAXIMIZE SW_MINIMIZE SW_NORMAL SW_HIDE 最大化、最小化、正常、隐藏显示 );
在调用ShowWindow函数之后,需要调用UpdateWindow来刷新窗口。
UpdateWindow(hWnd);//hWnd是创建成功后的窗口句柄
注意:UpdateWindow函数通过发送一个WM_PAINT消息来刷新窗口,UpdateWindow将WM_PAINT消息直接发送给了窗口过程函数进行处理,而没有放到消息队列中。
消息循环
在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,调用GetMessage函数不断从消息队列中取出消息,并进行相应。
消息结构体:
// typedef struct tagMSG { //HWND hwnd; //消息发向窗口的窗口句柄(指的是这个消息发个哪个窗口了,这里指定这个窗口的句柄) //UINT message; //消息编号 //WPARAM wParam; //附加消息 //LPARAM lParam;//附加消息 //DWORD time;//消息放入消息队列的时间 //POINT pt;//消息放入消息队列时鼠标坐标
BOOL GetMessage( LPMSG lpMsg,//消息结构体地址 HWND hWnd,//窗口句柄 UINT wMsgFilterMin,//要获取的消息的最小值,通常设置为0 UINT wMsgFilterMax//要获取的消息的最大值。如果FiterMin,Max两者都为0,则接收所有消息 );
代码如下:
MSG msg; //GetMessage:何时返回FALSE //当获取到WM_QUIT消息时,返回FALSE,没有获取到这个消息时,返回非0,不会退出循环 while (GetMessage(&msg, NULL, 0, 0)) { //将虚拟键消息转换为字符消息 TranslateMessage(&msg); //将消息分发给窗口处理函数 DispatchMessage(&msg); }
此处的Windows应用消息的消息处理机制如下图:
(1)os接收到应用消息的窗口消息【比如当用户在窗口中画图的时候,按下鼠标左键,此时os会感知到这一事件,于是将此事件包装成一个消息】,将消息投递到该应用消息的消息队列中。
(2)应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,比如放弃对某些消息的相应或调用TranslateMessage产生新的消息。
(3)应用程序调用DispatchMessage将消息回传给os。消息MSG结构体中包含接受消息的窗口的句柄。因此DispatchMessage函数总能进行正确传递。
(4)os利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。
附录完整代码
#include<Windows.h> #include<tchar.h> #include"resource.h" //LRESULT :long // UINT:unsigned int //WPARAM:unsigned int //LPARAM:unsigned int //LPCWSTR:const w_chart_t *; // typedef WORD ATOM; //BUGBUG - might want to remove this from minwin // typedef unsigned short WORD; // typedef unsigned long DWORD; //LPCWSTR const w_char_t * 宽字符 //LPCSTR const char * //窗口处理函数 //第一个参数:当前窗口句柄 //第二个参数:消息类型 //第三个参数:附加消息、附加操作 //第四个参数:附加消息、附加操作 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CLOSE://窗口关闭消息 DestroyWindow(hWnd);//销毁窗口,干掉界面,不会发出WM_QUIT,会发出一个WM_DESTORY消息 break; case WM_QUIT://窗口销毁消息 PostQuitMessage(0);//发布WM_QUIT break; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow) { //创建一个窗口的流程 //1设计窗口类 /* typedef struct tagWNDCLASSW { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCWSTR lpszMenuName; LPCWSTR lpszClassName; } WNDCLASSW,*/ //typedef WNDCLASSW WNDCLASS; //LRESULT CALLBACK WindowProc( // _In_ HWND hwnd, // _In_ UINT uMsg, // _In_ WPARAM wParam, // _In_ LPARAM lParam //); //typedef LRESULT(CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM); wchar_t szAppclassName[] = _T("FirstWin32"); WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;//窗口类的风格 //WNDPROC 函数指针类型 wc.lpfnWndProc = WindowProc;//窗口回调函数/窗口处理函数 wc.cbClsExtra = 0;//窗口类的附加内存大小 wc.cbWndExtra = 0;//窗口附加内存大小 wc.hInstance = hInstance;//当前应用程序实例句柄 wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));//加载自定义的图标句柄 //wc.hCursor = LoadCursor(NULL,IDC_CROSS);//光标句柄;加载系统光标,也可以采用下面的方式加载自定义的光标 wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1)); wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 0));//红绿蓝三原色0~255,[0最暗,255最亮] wc.lpszMenuName = NULL;//菜单名 wc.lpszClassName = szAppclassName;//窗口类型名 spy++(vs->工具选项) //2注册窗口类 //返回值ATOM if (0 == RegisterClass(&wc)) { MessageBox(NULL, _T("此程序不能复制在winNT平台"), _T("提示"), MB_OK); return 0; } //3创建窗口 //WS:window style HWND hWnd = CreateWindow(szAppclassName, //窗口类型名 _T("这个是我的第一个windows应用程序") //窗口左上角标题 , WS_BORDER | WS_CAPTION | WS_MAXIMIZEBOX | WS_SYSMENU, //窗口的风格 200, 300, //窗口左上角坐标 800, 600, // //窗口宽和高 NULL, //父窗口句柄 NULL, //菜单句柄 hInstance, //应用程序实例句柄 NULL); //创建窗口的附加参数,WM_CREATE消息,lparam来接受这个参数 if (NULL == hWnd) { MessageBox(NULL, _T("创建窗口失败"), _T("提示"), MB_OK); } //4显示窗口 //SW_SHOW:原来在何处显示,就在此处显示 //SW_MAXIMIZE SW_MINIMIZE SW_NORMAL SW_HIDE 最大化、最小化、正常、隐藏显示 ShowWindow(hWnd, SW_SHOW); //5更新窗口 UpdateWindow(hWnd); //6消息循环 // typedef struct tagMSG { //HWND hwnd; //消息发向窗口的窗口句柄(指的是这个消息发个哪个窗口了,这里指定这个窗口的句柄) //UINT message; //消息编号 //WPARAM wParam; //附加消息 //LPARAM lParam;//附加消息 //DWORD time;//消息放入消息队列的时间 //POINT pt;//消息放入消息队列时鼠标坐标 //windows程序都是通过消息机制驱动运行的。 MSG msg; //GetMessage:何时返回FALSE //当获取到WM_QUIT消息时,返回FALSE,没有获取到这个消息时,返回非0,不会退出循环 while (GetMessage(&msg, NULL, 0, 0)) { //将虚拟键消息转换为字符消息 TranslateMessage(&msg); //将消息分发给窗口处理函数 DispatchMessage(&msg); } MessageBox(NULL, _T("这是第一个win32应用程序"), _T("提示"), MB_OK); return 0; }