Windows程序设计--(三)窗口与消息
3.1 窗口的创建
3.1.1 系统结构概述
所谓「Windows给程序发送消息」,是指Windows呼叫程序中的一个函数,该函数的参数描述了这个特定消息。这种位于Windows程序中的函数称为「窗口消息处理程序」。
3.1.2 HELLOWIN
#include <Windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);//消息函数声明 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)//主函数 { static TCHAR szAppName[] = TEXT("WNDCLASS NAME");//窗口类名称 HWND hwnd;//句柄 MSG msg;//结构体 WNDCLASS wndclass;//窗口类 //窗口类属性 wndclass.style = CS_HREDRAW | CS_VREDRAW;//样式 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(DKGRAY_BRUSH);//主窗口背景色 wndclass.lpszMenuName = NULL;//窗口菜单 wndclass.lpszClassName = szAppName;//窗口类名 if (!RegisterClass(&wndclass)) {//注册窗口类,如果注册失败弹出窗口 MessageBox(NULL, TEXT("窗口创建失败!程序需要Windows NT!(传递窗口消息为UNICODE)"), szAppName, MB_ICONERROR);//消息窗口 return 0; } hwnd = CreateWindow(szAppName, //Windows类名 TEXT("窗口绘制成功!"), //窗口标题 WS_OVERLAPPEDWINDOW, //窗口风格 CW_USEDEFAULT, //初始化窗口位置的X坐标 CW_USEDEFAULT, //初始化窗口位置的Y坐标 CW_USEDEFAULT, //初始化窗口宽度大小 CW_USEDEFAULT, //初始化窗口长度大小 NULL, //父类窗口句柄 NULL, //窗口菜单句柄 hInstance, //程序实例句柄 NULL); //创建参数 ShowWindow(hwnd, iCmdShow);//显示窗口 UpdateWindow(hwnd);//更新窗口 while (GetMessage(&msg, NULL, 0, 0)) {//从消息队列中获取消息 TranslateMessage(&msg);//将虚拟键消息转换为字符消息 DispatchMessage(&msg);//分发到回调函数 } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc;//设备环境句柄 PAINTSTRUCT ps;//绘制结构 /* typedef struct tagRECT { LONG left; LONG top; LONG right; LONG bottom; } RECT 其中left,top赋为0,因此right和bottom表示客户区的宽度和高度(像素) */ RECT rect;//矩形结构 switch (message) {//处理得到的消息 case WM_CREATE://窗口创建发来消息 MessageBox(hwnd, TEXT("创建成功,音乐播放"), TEXT("Windows"), MB_OK | MB_ICONINFORMATION); /* 1.波形文件的名称 2.只有当声音文件是一个资源时才有用,此处NULL表示不使用 3.指定一组选项,表示指定了第一个参数为文件名且该段声音是以异步方式播放 */ PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC); return 0; case WM_PAINT://处理窗口绘制 hdc = BeginPaint(hwnd, &ps);//标明窗口绘制开始,设备环境句柄 GetClientRect(hwnd, &rect);//获取窗口客户区的尺寸 /* 1.为环境句柄 2.为要绘制的文本内容 3.设置为-1,表示文本字符串以0结尾 4.传递rect结构体 5.是一组位标记 */ DrawText(hdc, TEXT("Hello Hk_Mayfly!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps);//结束窗口绘制 return 0; case WM_LBUTTONDOWN: MessageBox(hwnd, TEXT("左键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION); return 0; case WM_RBUTTONDOWN: MessageBox(hwnd, TEXT("右键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION); return 0; case WM_DESTROY://处理窗口关闭时的消息 MessageBox(hwnd, TEXT("主窗口关闭"), TEXT("Windows"), MB_OK | MB_ICONINFORMATION);//显示一个文本字符串 PostQuitMessage(0);//将退出消息插入消息队列,程序从消息循环退出,return msg.wParam return 0; } return DefWindowProc(hwnd, message, wParam, lParam);//执行默认消息处理 }
3.1.3 通盘考虑
Windows API离线查询下载:http://download.microsoft.com/download/1/f/0/1f07c259-7ff2-4902-9205-ad1dfb87ccab/VS2008SP1MSDNENUX1506188.iso
上面调用了
函数名 | 函数描述 |
GetStockObject |
获取一个图形对象 |
LoadIcon |
为程序加载图标 |
LoadCursor |
为程序加载光标 |
RegisterClass |
为程序窗口注册一个窗口类 |
MessageBox |
显示消息对话框 |
CreateWindow |
创建一个窗口 |
ShowWindow |
在屏幕上将窗口显示出来 |
UpdateWindow |
重绘窗口客户区 |
GetMessage |
从消息队列获取消息 |
TranslateMessage |
将虚拟键消息转换为字符消息 |
DispatchMessage |
将消息发送给消息处理函数 |
BeginPaint |
准备对窗口进行绘图 |
GetClientRect |
获取窗口客户区尺寸 |
DrawText |
绘制一个文本字符串 |
EndPaint |
结束对窗口的绘图 |
PostQuitMessage |
向消息队列插入"退出"消息 |
DefWindowProc |
执行系统默认的消息处理 |
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_ |
窗口风格 |
匈牙利标记法
匈牙利命名法是一种编程时的命名规范。基本原则是:变量名=属性+类型+对象描述,其中每一对象的名称都要求有明确含义,可以取对象名字全称或名字的一部分。
例如,szCmdLine中的sz代表「以0结尾的字符串」。在hInstance和hPrevInstance中的h前缀表示「句柄」;在iCmdShow中的i前缀表示「整数」。WndProc的后两个参数也使用匈牙利表示法。正如我在前面已经解释过的,尽管wParam应该更适当地被命名为uiParam(代表「无正负号整数」),但是因为这两个参数是使用数据型态WPARAM和LPARAM定义的,因此保留它们传统的名字。
3.1.4 窗口类的注册
在为程序建立窗口之前,必须首先呼叫RegisterClass注册一个窗口类别。该函数只需要一个参数,即一个指向型态为WNDCLASS的结构指针。此结构包括两个指向字符串的字段,因此结构在WINUSER.H表头文件中定义了两种不同的方式,第一个是ASCII版的WNDCLASSA
typedef struct tagWNDCLASSA { UINT style ; WNDPROC lpfnWndProc ; int cbClsExtra ; int cbWndExtra ; HINSTANCE hInstance ; HICON hIcon ; HCURSOR hCursor ; HBRUSH hbrBackground ; LPCSTR lpszMenuName ; LPCSTR lpszClassName ; } WNDCLASSA, * PWNDCLASSA, NEAR * NPWNDCLASSA, FAR * LPWNDCLASSA ;
在这里提示一下数据型态和匈牙利表示法:其中的lpfn前缀代表「指向函数的长指标」。(在Win32 API中,长指标和短指标(或者近程指标)没有区别。这只是16位Windows的遗物。)cb前缀代表「字节数」而且通常作为一个常数来表示一个字节的大小。h前缀是一个句柄,而hbr前缀代表「一个画刷的代号」。lpsz前缀代表「指向以0结尾字符串的指针」。
Unicode版的结构定义如下:
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 ;
与前者唯一的区别在于最后两个字段定义为指向宽字符串常数,而不是指向ASCII字符串常数。
在WinMain中定义WNDCLASS类型结构
WNDCLASS wndclass;
对该结构的10个字段进行初始化,并调用RegisterClass
位标记
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
代码中用到的这两个标识符表示,所有依据此类别建立的窗口,每当窗口的水平方向大小(CS_HREDRAW)或者垂直方向大小(CS_VREDRAW)改变之后,窗口要完全重画。
在表头文件WINUSER.H中,已定义了一整组以CS为前缀的标识符:
#define CS_VREDRAW 0x0001 #define CS_HREDRAW 0x0002 #define CS_KEYCVTWINDOW 0x0004 #define CS_DBLCLKS 0x0008 #define CS_OWNDC 0x0020 #define CS_CLASSDC 0x0040 #define CS_PARENTDC 0x0080 #define CS_NOKEYCVT 0x0100 #define CS_NOCLOSE 0x0200 #define CS_SAVEBITS 0x0800 #define CS_BYTEALIGNCLIENT 0x1000 #define CS_BYTEALIGNWINDOW 0x2000 #define CS_GLOBALCLASS 0x4000 #define CS_IME 0x00010000
wndclass.lpfnWndProc = WndProc ;
将窗口消息处理程序设定为WndProc函数(我们自定义的函数)
wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ;
这两个字段用于在窗口类别结构和Windows内部保存的窗口结构中预留一些额外空间
wndclass.hInstance = hInstance ;
程序的执行实体句柄(WinMain参数之一)
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
第一个参数是取得预先定义图示的句柄。第二个参数代表图示。对于预先定义图示,此参数是以IDI开始的标识符(「ID代表图示」),标识符在WINUSER.H中定义。
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
LoadCursor函数加载一个预先定义的鼠标光标(命名为IDC_ARROW),并传回该游标的句柄。该句柄被设定给WNDCLASS结构的hCursor字段。当鼠标光标在依据这个类别建立的窗口的显示区域上出现时,它变成一个小箭头。
wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
依据这个类别建立的窗口背景颜色。这里所示的GetStockObject呼叫将传回一个白色画刷的句柄:
wndclass.lpszMenuName = NULL ;
指定窗口类别菜单。HElLOWIN没有应用程序菜单,所以该字段被设定为NULL
wndclass.lpszClassName = szAppName ;
类别名称
if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; }
Windows 98不支持Unicode程序
3.1.5 窗口的创建
窗口创建
HWND CreateWindow( LPCTSTR lpClassName, //窗口类名称 LPCTSTR lpWindowName, //窗口标题 DWORD dwStyle, //窗口样式 int x, //窗口初始x坐标 int y, //窗口初始y坐标 int nWidth, //窗口初始x方向尺寸 int nHeight, //窗口初始y方向尺寸 HWND hWndParent, //父窗口句柄 HMENU hMenu, //窗口菜单句柄 HANDLE hlnstance, //程序实例句柄 LPVOID lpParam //创建参数 );
hwnd = CreateWindow(szAppName, //Windows类名 TEXT("窗口绘制成功!"), //窗口标题 WS_OVERLAPPEDWINDOW, //窗口风格 CW_USEDEFAULT, //初始化窗口位置的X坐标 CW_USEDEFAULT, //初始化窗口位置的Y坐标 CW_USEDEFAULT, //初始化窗口宽度大小 CW_USEDEFAULT, //初始化窗口长度大小 NULL, //父类窗口句柄 NULL, //窗口菜单句柄 hInstance, //程序实例句柄 NULL); //创建参数
关于窗口风格除了WS_OVERLAPPEDWINDOW还有
#define WS_OVERLAPPED 0x00000000L //产生一个层叠的窗口。一个层叠的窗口有一个标题条和一个边框。与WS_TILED风格相同。 #define WS_POPUP 0x80000000L //创建一个弹出式窗口。该风格不能与WS_CHLD风格同时使用。 #define WS_CHILD 0x40000000L //创建一个子窗口。这个风格不能与WS_POPUP风格合用。 #define WS_MINIMIZE 0x20000000L //创建一个初始状态为最小化的窗口。仅与WS_OVERLAPPED风格一起使用。 #define WS_VISIBLE 0x10000000L //创建一个最初可见的窗口。 #define WS_DISABLED 0x08000000L //创建一个初始状态为禁止的窗口。 #define WS_CLIPSIBLINGS 0x04000000L //排除子窗口之间的相对区域。 #define WS_CLIPCHILDREN 0x02000000L //创建一个初始状态为禁止的子窗口。一个禁止状态的窗口不能接受来自用户的输入信息。 #define WS_MAXIMIZE 0x01000000L //创建一个初始状态为最大化状态的窗口。 #define WS_CAPTION 0x00C00000L //创建一个有标题框的窗口(包括WS_BODER风格)。 #define WS_BORDER 0x00800000L //创建一个单边框的窗口。 #define WS_DLGFRAME 0x00400000L //创建一个带对话框边框风格的窗口。这种风格的窗口不能带标题条。 #define WS_VSCROLL 0x00200000L //创建一个有垂直滚动条的窗口。 #define WS_HSCROLL 0x00100000L //创建一个有水平滚动条的窗口。 #define WS_SYSMENU 0x00080000L //创建一个在标题条上带有窗口菜单的窗口,必须同时设定WS_CAPTION风格。 #define WS_THICKFRAME 0x00040000L //创建一个具有可调边框的窗口,与WS_SIZEBOX风格相同。 #define WS_GROUP 0x00020000L //指定一组控制的第一个控制。 #define WS_TABSTOP 0x00010000L //创建一个控制,这个控制在用户按下Tab键时可以获得键盘焦点。按下Tab键后使键盘焦点转移到下一具有WS_TABSTOP风格的控制。 #define WS_MINIMIZEBOX 0x00020000L //创建一个具有最小化按钮的窗口。 #define WS_MAXIMIZEBOX 0x00010000L //创建一个具有最大化按钮的窗口。 #define WS_TILED WS_OVERLAPPED //产生一个层叠的窗口。一个层叠的窗口有一个标题和一个边框。与WS_OVERLAPPED风格相同。 #define WS_ICONIC WS_MINIMIZE //创建一个初始状态为最小化的窗口。仅与WS_OVERLAPPED风格一起使用。 #define WS_SIZEBOX WS_THICKFRAME //创建一个具有厚边框的窗口,可以通过厚边框来改变窗口大小。 #define WS_TILEDWINDOW WS_OVERLAPPEDWINDOW //创建一个具有WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU,WS_THICKFRAME,WS_MINIMIZEBOX和WS_MAXIMIZEBOX风格的重叠式窗口。
参数使用CW_USEDEFAULT标识符,指示Windows使用重迭窗口的内定位置。(CW_USEDEFAULT定义为0x80000000)表示使用默认的Windows参数值,也可以使用自定义数值。
3.1.6 窗口的显示
程序通过执行一块称之为「消息循环」的程序代码从消息队列中取出消息:
while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; }
msg变量是型态为MSG的结构,型态MSG在WINUSER.H中定义如下:
typedef struct tagMSG { HWND hwnd ; UINT message ; WPARAM wParam ; LPARAM lParam ; DWORD time ; POINT pt ; } MSG, * PMSG ;
POINT数据型态也是一个结构,它在WINDEF.H中定义如下:
typedef struct tagPOINT { LONG x ; LONG y ; } POINT, * PPOINT;
消息循环以GetMessage呼叫开始,它从消息队列中取出一个消息:
GetMessage (&msg, NULL, 0, 0)
TranslateMessage (&msg) ;
将msg结构传给Windows,进行一些键盘转换。
DispatchMessage (&msg) ;
又将msg结构回传给Windows。然后,Windows将该消息发送给适当的窗口消息处理程序,让它进行处理。这也就是说,Windows将呼叫窗口消息处理程序。
3.1.8 窗口过程
窗口消息处理程序总是定义为如下形式:
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
注意,窗口消息处理程序的四个参数与MSG结构的前四个字段是相同的。第一个参数hwnd是接收消息的窗口的句柄,它与CreateWindow函数的传回值相同。对于与HELLOWIN相似的程序(只建立一个窗口),这个参数是程序所知道的唯一窗口句柄。如果程序是依据同一窗口类别(同时也是同一窗口消息处理程序)建立多个窗口,则hwnd标识接收消息的特定窗口。
第二个参数与MSG结构中的message字段相同,它是标识消息的数值。最后两个参数都是32位的消息参数,提供关于消息的更多信息。这些参数包含每个消息型态的详细信息。有时消息参数是两个存放在一起的16位值,而有时消息参数又是一个指向字符串或数据结构的指针。
程序通常不直接呼叫窗口消息处理程序,窗口消息处理程序通常由Windows本身呼叫。通过呼叫SendMessage函数,程序能够直接呼叫它自己的窗口消息处理程序。我们将在后面的章节讨论SendMessage函数。
3.1.9 消息处理
使用switch...case来处理
switch (message) {//处理得到的消息 case WM_CREATE://窗口创建发来消息 MessageBox(hwnd, TEXT("创建成功,音乐播放"), TEXT("Windows"), MB_OK | MB_ICONINFORMATION); PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC); return 0; case WM_PAINT://处理窗口绘制 hdc = BeginPaint(hwnd, &ps);//标明窗口绘制开始,设备环境句柄 GetClientRect(hwnd, &rect);//获取窗口客户区的尺寸 DrawText(hdc, TEXT("Hello Hk_Mayfly!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps);//结束窗口绘制 return 0; case WM_LBUTTONDOWN: MessageBox(hwnd, TEXT("左键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION); return 0; case WM_RBUTTONDOWN: MessageBox(hwnd, TEXT("右键按下!"), TEXT("BIU"), MB_OK | MB_ICONINFORMATION); return 0; case WM_DESTROY://处理窗口关闭时的消息 MessageBox(hwnd, TEXT("主窗口关闭"), TEXT("Windows"), MB_OK | MB_ICONINFORMATION);//显示一个文本字符串 PostQuitMessage(0);//将退出消息插入消息队列,程序从消息循环退出,return msg.wParam return 0; } return DefWindowProc(hwnd, message, wParam, lParam);//执行默认消息处理
3.1.10 声音文件的播放
3.1.11 WM_PAINT消息
当窗口显示区域的一部分显示内容或者全部变为「无效」,以致于必须「更新画面」时,将由这个消息通知程序。‘
对WM_PAINT的处理几乎总是从一个BeginPaint呼叫开始:
hdc = BeginPaint (hwnd, &ps) ;
而以一个EndPaint呼叫结束:
EndPaint (hwnd, &ps) ;
在这两个呼叫中,第一个参数都是程序的窗口句柄,第二个参数是指向型态为PAINTSTRUCT的结构指针。
呼叫完BeginPaint之后,WndProc接着呼叫GetClientRect:
GetClientRect (hwnd, &rect) ;
第一个参数是程序窗口的句柄。第二个参数是一个指标,指向一个RECT型态的rectangle结构。该结构有四个LONG字段,分别为left、top、right和bottom。GetClientRect将这四个字段设定为窗口显示区域的尺寸。left和top字段通常设定为0,right和bottom字段设定为显示区域的宽度和高度(像素点数)。
WndProc除了将该RECT结构指针作为DrawText的第四个参数传递外,不再对它做其它处理:
DrawText ( hdc, TEXT ("Hello, Windows 98!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
DrawText可以输出文字(正如其名字所表明的一样)。由于该函数要输出文字,第一个参数是从BeginPaint传回的设备内容句柄,第二个参数是要输出的文字,第三个参数是 -1,指示字符串是以字节0终结的。DrawText最后一个参数是一系列位旗标
常用的有:
DT_BOTTOM //将正文调整到矩形底部; DT_CENTER //使正文在矩形中水平居中; DT_LEFT //正文左对齐; DT_RIGHT //正文右对齐; DT_TOP //正文顶端对齐; DT_VCENTER //使正文在矩形中垂直居中; DT_WORD_ELLIPSIS //截短不符合矩形的正文,并增加省略号;
3.1.12 WM_DESTROY消息
程序通过呼叫PostQuitMessage以标准方式响应WM_DESTROY消息:
PostQuitMessage (0) ;
该函数在程序的消息队列中插入一个WM_QUIT消息。前面提到过,GetMessage对于除了WM_QUIT之外的从消息队列中取出的所有消息都传回非0值。而当GetMessage得到一个WM_QUIT消息时,它传回0。这将导致WinMain退出消息循环,并终止程序。然后程序执行下面的叙述:
return msg.wParam ;
结构的wParam字段是传递给PostQuitMessage函数的值(通常是0)。然后return叙述将退出WinMain并终止程