对窗口创建的详细解释
============
在昨天的学习中, 我们已经初步完成了一个窗口的创建, 虽然我们对于代码中的许多语句可能还不理解, 但这暂时并不会影响到我们的学习。再次来回顾昨天那个窗口的代码部分:
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 API
通过这段代码, 我们完成了在客户区(窗口的白色区域)中心显示一段字符串, 此外, 我们还处理了系统发来的部分消息, 在这个过程中我们使用了Windows的诸多函数, 这些函数分别为:
函数名 | 函数描述 |
GetStockObject |
获取一个图形对象 |
LoadIcon |
为程序加载图标 |
LoadCursor |
为程序加载光标 |
RegisterClass |
为程序窗口注册一个窗口类 |
MessageBox |
显示消息对话框 |
CreateWindow |
创建一个窗口 |
ShowWindow |
在屏幕上将窗口显示出来 |
UpdateWindow |
重绘窗口客户区 |
GetMessage |
从消息队列获取消息 |
TranslateMessage |
将虚拟键消息转换为字符消息 |
DispatchMessage |
将消息发送给消息处理函数 |
BeginPaint |
准备对窗口进行绘图 |
GetClientRect |
获取窗口客户区尺寸 |
DrawText |
绘制一个文本字符串 |
EndPaint |
结束对窗口的绘图 |
PostQuitMessage |
向消息队列插入"退出"消息 |
DefWindowProc |
执行系统默认的消息处理 |
对于这些函数的详细说明, 可以到百科查询, 或者到MSDN Library文档中查询(推荐)。
·大写标识符说明
在上面的程序中出现了许多类似于XX_XXXX的大写标识符, 如:
CS_HREDRAW |
CS_VREDRAW |
IDI_APPLICATION |
IDC_ARROW |
MB_OK |
MB_ICONERROR |
WS_OVERLAPPEDWINDOW |
CW_USEDEFAULT |
DT_SINGLELINE |
DT_CENTER |
DT_VCENTER |
WM_CREATE |
WM_PAINT |
WM_LBUTTONDOWN |
WM_DESTROY |
这些标识符均为数值常量, 在WINUSER.H头文件中有定义, 该程序中用到的一些标识符前缀含义为:
前缀 |
含义 |
CS_ |
类风格选项 |
CW_ |
创建窗口选项 |
DT_ |
文本绘制选项 |
IDI_ |
图标的ID号 |
IDC_ |
光标的ID号 |
MB_ |
消息框选项 |
WM_ |
窗口消息 |
WS_ |
窗口风格 |
对于这些大写标识符暂时没必要强记下来, 只需要对其有个大致的印象即可, 不会影响到我们的学习。
·"新"数据类型
在调用的函数中, 一些函数的参数类型是我们以往在C控制台编程中所没有见到过的, 例如在代码的第三行对回调函数的声明中:
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //声明用来处理消息的函数
其中的"LRESULT"、"UINT"、"WPARAM"、"LPWARAM", 以及主函数WinMain中的"PSTR"之类的类型都是我们所没有见过的, 这些"新"数据类型都是什么呢?
其实这些"新"数据类型是为了程序书写时的简便而使用typedef或#define对C已有的数据类型重新定义的, 在WINDEF.H/WINNT.H头文件中有定义, 如:
typedef unsigned int UINT; typedef char CHAR; typedef CHAR *LPSTR, *PSTR; typedef UINT WPARAM; typedef long LONG; typedef LONG LPARAM; typedef LONG LRESULT;
可以看出, UINT实际上就是unsigned int型; PSTR就是一个char *型, WPARAM被定义为UINT型, 也就是unsigned int型; LPARAM和LRESULT被定义为long型;
由此观之, "新"数据类型, 并不新。
在声明消息处理函数WndProc时, 在函数名前面我们还是使用了一个"CALLBACK"标识符, 这个标识符其实就是函数的调用规则, 在WINDEF.H中有定义:
#define CALLBACK __stdcall
在前些天的学习中, 我们还知道在WinMain主函数的前面有个WINAPI调用规则, 在WINDEF.H中也被定义为 __stdcall。
·结构类型
结构体, 在C语言的学习中我们已经对这种数据类型十分熟悉了, 在上述创建窗口的过程中我们使用了四种结构类型来创建结构体的对象, 这些结构体在WINUSER.H/WINDEF.H中有定义, 现在我们只需对其进行一下印象中的认识, 对于结构体的成员暂时不去讨论, 这四种结构体分别为:
1>. MSG消息结构
MSG在WINUSER.H中的定义:
typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; #ifdef _MAC DWORD lPrivate; #endif } MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
2>. WNDCLASS窗口类结构
WNDCLASS在WINUSER.H中的定义:
typedef struct tagWNDCLASSW { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCWSTR lpszMenuName; LPCWSTR lpszClassName; } WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW; #ifdef UNICODE typedef WNDCLASSW WNDCLASS;
3>. PAINTSTRUCT绘制结构
PAINTSTRUCT在WINUSER.H中的定义:
typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; } PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;
4>. RECT矩形结构
RECT在WINDEF.H中的定义:
typedef struct tagRECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;
·再谈句柄
不同类型的句柄有不同的标识符, 在我们尝试创建窗口的代码中用到的句柄有:
标识符 |
含义 |
HINSTANCE |
实例句柄, 指程序本身 |
HWND |
窗口句柄 |
HDC |
设备环境句柄 |
HBRUSH |
图形画刷句柄 |
句柄是一个标识符,用来来标识对象, 一个句柄使用四个字节长的整数来存储一个整数值, 这个具体的整数值实际上我们并不需要知道是多少, 我们要做的就是传递句柄, Windows会知道如果用过这个句柄找到并以引用相应的对象。
·匈牙利命名法
匈牙利命名法是一种编程时的命名规范, 在一开始我们接触的MessageBox对话框时我们就已经见到了匈牙利命名的变量:
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow )
这里的 hInstance、hPrevInstance、szCmdLine、iCmdShow的命名都遵循匈牙利命名法, 匈牙利命名法的目的和作用是使变量名非常清晰易懂,增强了代码的可读性,方便各程序员之间相互交流代码, 减少代码中因数据类型不匹配的错误。
例如:
szCmdLine, 通过变量名我们就可以知道这是一个"以0结束的字符串";
hInstance, 以h开头, 代表一个句柄;
iCmdShow, 以i开头, 代表一个int整形。
这样, 就可以不用再去找到变量声明的位置去看它的变量类型, 避免了在使用变量时由于数据类型不匹配引起的错误。
经常使用的匈牙利命名的前缀如下:
前缀 | 含义描述 |
p | 指针 |
fn | 函数 |
v | 无效 |
h | 句柄 |
l | 长整形 |
b | 布尔型 |
f | 浮点型 |
dw | 双字 |
sz | 字符串 |
n | 短整型 |
d | 双精度浮点型 |
c | 计数, 通常写为cnt |
ch | 字符, 通常写为c |
i | 整型 |
by | 字节 |
w | 字型 |
r | 实型 |
u | 无符号型 |
g_ | 全局变量 |
c_ | 常量 |
m_ | 成员变量 |
s_ | 静态变量 |
Max | 最大 |
Min | 最小 |
Init | 初始化 |
T 或 Temp | 临时变量 |
Src | 源对象 |
Dest | 目标对象 |
-------------------