对窗口创建的详细解释
==================
回顾示例中创建窗口的代码:
1 #include <windows.h> 2 3 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //声明用来处理消息的函数 4 5 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) 6 { 7 static TCHAR szAppName[] = TEXT("MyWindow") ; 8 HWND hwnd ; 9 MSG msg ; 10 WNDCLASS wndclass ; //声明一个窗口类对象 11 12 //以下为窗口类对象wndclass的属性 13 wndclass.style = CS_HREDRAW | CS_VREDRAW ; //窗口样式 14 wndclass.lpszClassName = szAppName ; //窗口类名 15 wndclass.lpszMenuName = NULL ; //窗口菜单:无 16 wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH) ; //窗口背景颜色 17 wndclass.lpfnWndProc = WndProc ; //窗口处理函数 18 wndclass.cbWndExtra = 0 ; //窗口实例扩展:无 19 wndclass.cbClsExtra = 0 ; //窗口类扩展:无 20 wndclass.hInstance = hInstance ; //窗口实例句柄 21 wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ) ; //窗口最小化图标:使用缺省图标 22 wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ) ; //窗口采用箭头光标 23 24 if( !RegisterClass( &wndclass ) ) 25 { //注册窗口类, 如果注册失败弹出错误提示 26 MessageBox( NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_OK | MB_ICONERROR ) ; 27 return 0 ; 28 } 29 30 hwnd = CreateWindow( //创建窗口 31 szAppName, //窗口类名 32 TEXT("我的窗口"), //窗口标题 33 WS_OVERLAPPEDWINDOW, //窗口的风格 34 CW_USEDEFAULT, //窗口初始显示位置x:使用缺省值 35 CW_USEDEFAULT, //窗口初始显示位置y:使用缺省值 36 CW_USEDEFAULT, //窗口的宽度:使用缺省值 37 CW_USEDEFAULT, //窗口的高度:使用缺省值 38 NULL, //父窗口:无 39 NULL, //子菜单:无 40 hInstance, //该窗口应用程序的实例句柄 41 NULL // 42 ) ; 43 44 ShowWindow( hwnd, iCmdShow ) ; //显示窗口 45 UpdateWindow( &msg ); //更新窗口 46 47 while( GetMessage( &msg, NULL, 0, 0 ) ) //从消息队列中获取消息 48 { 49 TranslateMessage( &msg ) ; //将虚拟键消息转换为字符消息 50 DispatchMessage( &msg ) ; //分发到回调函数(过程函数) 51 } 52 return msg.wParam ; 53 } 54 55 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 56 { 57 HDC hdc ; //设备环境句柄 58 PAINTSTRUCT ps ; //绘制结构 59 RECT rect; //矩形结构 60 61 switch( message ) //处理得到的消息 62 { 63 case WM_CREATE: //窗口创建完成时发来的消息 64 MessageBox( hwnd, TEXT("窗口已创建完成!"), TEXT("我的窗口"), MB_OK | MB_ICONINFORMATION ) ; 65 return 0; 66 67 case WM_PAINT: //处理窗口区域无效时发来的消息 68 hdc = BeginPaint( hwnd, &ps ) ; 69 GetClientRect( hwnd, &rect ) ; 70 DrawText( hdc, TEXT( "Hello, 这是我自己的窗口!" ), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER ) ; 71 EndPaint( hwnd, &ps ) ; 72 return 0 ; 73 74 case WM_LBUTTONDOWN: //处理鼠标左键被按下的消息 75 MessageBox( hwnd, TEXT("鼠标左键被按下。"), TEXT("单击"), MB_OK | MB_ICONINFORMATION ) ; 76 return 0; 77 78 case WM_DESTROY: //处理窗口关闭时的消息 79 MessageBox( hwnd, TEXT("关闭程序!"), TEXT("结束"), MB_OK | MB_ICONINFORMATION ) ; 80 PostQuitMessage( 0 ) ; 81 return 0; 82 } 83 return DefWindowProc( hwnd, message, wParam, lParam ) ; //DefWindowProc处理我们自定义的消息处理函数没有处理到的消息 84 }
·消息的循环
"消息", 在Windows程序设计中是个十分重要的概念, 是消息驱动着Windows系统的运行。通过昨天的学习, 我们已经知道一个窗口是如何被创建并且显示在屏幕上, 当一个窗口被创建之后, Windows就开始向窗口源源不断的发送一些消息, 消息来自Windows的为该程序创建的一个消息队列中, 然后窗口过程根据不同的消息作出相应的处理。
Windows中的"消息"(MSG)是一个结构类型, 该结构定义在WINUSER.H头文件中, MSG结构的定义如下:
/* * Message structure */ typedef struct tagMSG{ HWND hwnd; //窗口句柄, 代表消息所属的窗口 UINT message; //消息的编号 WPARAM wParam; //附加信息 LPARAM lParam; //附加信息 DWORD time; //消息的时间 POINT pt; //鼠标的位置 } MSG, *PMSG;
成员HWND为消息所指向的窗口句柄, 代表消息所属的窗口; UINT为消息的编号, 为一个unsigned int型数据; WPARAM和LPARAM用于指定消息的附加信息, 具体含义取决于具体的消息; DWORD为unsigned long型, 为消息进入消息队列时的时间; POINT也是一种结构类型, 在WINDEF.H中的定义如下:
typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT;
在这里用来记录消息进入消息队列时的鼠标位置。
当消息产生后会存在于Windows为该程序维护的一个消息队列里, 要得到这些消息我们需要使用GetMessage函数, 此函数的作用是消息队列里取得一个消息并将该消息存放在指定的一个MSG结构的一个对象中, 函数的原型如下:
BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ) ;
参数一LPMSG lpMsg为指向一个MSG结构型的指针, 用于从消息队列中接收消息; 参数二为待检索消息的窗体句柄, 通常设为NULL, 第三、四个参数UINT wMsgFilterMin, UINT wMsgFilterMax分别代表允许被检索的最小消息值的整数值和最大消息值的整数值, 当成功取得消息时, 该消息就会被从消息队列中删除(WM.PAINT消息是个例外)。
函数的返回值, 成功取得除WM_QUIT之外的消息返回值为非零, 取得WM_QUIT消息时, 返回值是零, 如果出现了错误, 返回值为-1。
在GetMessage后我们还看到使用了个TranslateMessage函数, 该函数用来将虚拟键消息转换为字符消息(暂时先了解下), 完成对消息的翻译转换后调用DispatchMessage函数将得到的消息再传递给Windows, 然后由Windows去调用我们的窗口过程(回调函数)。
窗口过程完成对消息的相关处理后再将控制权转回给Windows, 继续下一轮的消息处理。
·对消息的处理
在以上的叙述中我们已经大致了解了一个窗口创建的工作流程, 其中我们多次提到了窗口过程, 也就是我们自定义的消息处理函数, 窗口过程决定对得到的消息以何种方式进行处理, 在我们创建窗口的示例中, 自定义的窗口过程名为WndProc, 窗口过程总是以以下形式声明:
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ;
窗口过程有四个参数, 分别对应这个MSG结构中的前四个成员。
关于窗口过程的使用, 是通过窗口类
wndclass.lpfnWndProc = WndProc ;
的这种方式引用的, 一个窗口过程总是与一个特定的窗口类进行关联。
例如在我们创建窗口的示例中分别对得到的四种消息进行处理, WM_CREATE、WM_PAINT、WM_LBUTTONDOWN和WM_DESTROY。在Windows中, 消息总是以WM为前缀作为消息类型的标示, 这些标识符在WINUSER.H头文件中有相关的定义, 关于这些的消息详细情况可以查阅MSDN Library。
接下来我们使用了个switch语句对得到的这些消息进行处理, 通常是以这样的形式出现:
switch( message ) { case 消息1: [对消息1进行处理] ; return 0 ; case 消息2: [对消息1进行处理] ; return 0 ; case 消息3: [对消息3进行处理] ; return 0 ; ...... case 消息n: [对消息n进行处理]; return 0; } return DefWindowProc( hwnd, message, wParam, lParam ) ;
这里需要注意2点, 一是在case语句对消息处理完毕后一般使用return 0;结束窗口过程, 对于如果使用break;语句函数将再次调用默认的消息处理函数DefWindowProc进行处理; 二是
DefWindowProc( hwnd, message, wParam, lParam );
有这条语句是十分必要的, 对于我们没有处理到的消息, 必须进行调用默认的消息处理函数DefWindowProc来确保得到的每个消息进行处理, DefWindowProc函数的原型如下:
LRESULT DefWindowProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM IParam ) ;
参数一HWND hWnd为接收消息的窗口句柄;
参数二UINT Msg,为需要处理的消息类型;
参数三wParam与参数四LPARAM IParam为额外的消息信息。该参数的内容与msg参数值有关。
返回值为消息处理的结果, 根据不同的消息返回值可能不同。
如果没有对其他消息使用默认的消息处理, 那么窗口的其他正常行为将无法进行。
现在, 我们来看下创建示例窗口中是如何对这4种消息进行处理的。
1>. 处理WM_CREATE 消息
WM_CREATE消息是窗口在成功创建时进入消息队列, 但值得一提的是: WM_CREATE在CreateWindow函数返回之前就被送到了消息队列当中。
我们对于WM_CREATE消息作出的响应是弹出一个对话框告诉用户窗口已被成功创建:
MessageBox( hwnd, TEXT("窗口已创建完成!"), TEXT("我的窗口"), MB_OK | MB_ICONINFORMATION ) ;
2>. 处理WM_PAINT消息
WM_PAINT消息是当窗口客户区无效并且需要重新更新时接收到的消息, 何时窗口客户区会变得无效且需要更新?例如以下情况:
1. 首次被创建时, 这时整个客户区都是无效的;
2. 调整窗口的尺寸时, 这时客户区也会变得无效;
3. 最小化窗口后再将其恢复显示时;
4. 拖拽调整窗口位置时;
当客户区变得无效时我们就需要对其进行重绘, 一般总是调用BeginPaint函数作为重绘的开始, BeginPaint函数的原型如下:
HDC BeginPaint( HWND hwnd, LPPAINTSTRUCT lpPaint );
参数一为程序被重绘的窗口句柄, 参数二为用来指向接收绘画信息的一个PAINTSTRUCT结构;
当函数调用成功, 返回值是指定窗口的“显示设备描述表”句柄; 如果函数失败,返回值是NULL。
通过设备环境句柄我们就可以在窗口的客户区进行绘制一些信息, 例如绘制图形或一些文本, 取得设备环境句柄:
hdc = BeginPaint( hwnd, &ps ) ;
成功获取到设备环境句柄后就可以开始对客户区进行重绘工作了, 首先使用GetClientRect函数获取该窗口客户区左上角(0, 0)和右下角(x, y)的坐标, 函数原型。
BOOL GetClientRect( HWND hWnd, // 窗口句柄 LPRECT lpRect //指向一个矩形结构用来接收客户区坐标 );
函数调用成功时,返回一个非零值; 调用失败, 返回零。
随后使用了DrawText向矩形结构里输入一些文字, DrawText函数的原型如下:
int DrawText( HDC hDC, // 设备描述表句柄 LPCTSTR lpString, // 将要绘制的字符串 int nCount, // 字符串的长度 LPRECT lpRect, // 指向矩形结构RECT的指针 UINT uFormat // 绘制选项 );
第一个参数为BeginPaint函数所返回的设备环境句柄;
第二个参数为要绘制的字符串,;
第三个参数为字符串的长度, 如果参数为-1, 则表示一个以0结尾的字符串, 让DrawText自己计算字符个数;
第四个参数为一组通过组合的标识符, 这些标识符用来标明文本的绘制方式, 例如较常用的一些有:
DT_BOTTOM //将正文调整到矩形底部; DT_CENTER //使正文在矩形中水平居中; DT_LEFT //正文左对齐; DT_RIGHT //正文右对齐; DT_TOP //正文顶端对齐; DT_VCENTER //使正文在矩形中垂直居中; DT_WORD_ELLIPSIS //截短不符合矩形的正文,并增加省略号;
此外还有更多的绘制方式, 这些标识符定义在WINUSER.H头文件中, 可以到MSDN Library查询更多详细的介绍。
绘制完成后使用EndPaint( hwnd, &ps ) ;释放设备环境句柄, 这样窗口的一遍重绘就完成了, 窗口客户区再次变得有效。
3>. 处理WM_LBUTTONDOWN 消息
WM_LBUTTONDOWN 消息是当鼠标左键在客户区被按下时产生的消息, 处理的过程为简单的弹出一个对话框提示鼠标左键被按下了。
4>.处理 WM_DESTROY 消息
当用户点击窗口上的关闭按钮时, WM_DESTROY 消息产生, 我们弹出一个"关闭程序!"的对话框后使用PostQuitMessage 函数向消息队列插入一个WM_QUIT, 当GetMessage获取到WM_QUIT消息时便返回一个0, 使while退出循环执行
return msg.wParam ; //msg.wParam为PostQuitMessage( 0 ); 函数中的参数, 通常为0
从而结束程序运行。
------------------
上一篇: C语言Windows程序设计->第四天->详解我的窗口(中)