Windows应用程序是由一系列的消息处理
代码来实现的。这和传统的过程式编程方法很不一样,编程者只能够
预测用户所利用应用程序用户界面对象所进行的操作以及为这些操作
编写处理代码,却不可以这些操作在什么时候发生或者是以什么顺序
来发生,也就是说,我们不可能知道什么消息会在什么时候以什么顺
序来临。
Windows应用程序基本流程:
Windows API:(windowsAppliacation Programming Interface. Windows 应用程序编程接口),
API就是一系列的例程,应用程序通过调用这些例程来请求操作系统完成一些低级服务。在Windows这样的图形用户界面中,应用程序的窗口、图标、菜单和对话框等就是由API来管理和维护的。
Windows应用程序中,main()函数被、WinMain()函数取代,WinMain()函数原型如下:
Int WINAPIWinMain( HINSTANCE hInstance //当前实例句柄
HINSTANCE hPrevInstance //前一实例句柄
LPSTR lpCmdLine //指向命令行参数的指针
Int nCmdShow)//窗口的显示状态
“句柄”(handle),所谓的句柄是一个标识
对象的变量,或者是一个对操作系统资源的间接引用。
WinMain()函数中一般需要完成以下操作:
1 注册窗口类
2 创建应用程序主窗口
3 进入应用程序消息循环
在注册窗口类前,我们先创建一个类型为
WNDCLASS的结构,然后在该结构对象中填入窗口类的信息,最后将它
传递给函数RegisterClass,整个过程如下面的代码所示:
WNDCLASS wc;
// 填充窗口类信息
wc.style=CS_HREDRAW|CS_VREDRAW;//
wc.lpfnWndProc=WndProc;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
wc.hInstance=hInstance;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=GetStockObject(WHITE_BRUSH);
wc.lpszMenuName=NULL;
wc.lpszClassName="SdkDemo1";
// 注册窗口类
RegisterClass(&wc);
在使用RegisterClass注册窗口类成功之后,即可以使用该窗口类创
建并显示应用程序的窗口。这个过程如下面的代码所示:
// 创建应用程序主窗口
hWnd=CreateWindow("SdkDemo1", // 窗口类名
"第一个Win32 SDK应用程序",// 窗口标题
WS_OVERLAPPEDWINDOW,// 窗口样式
CW_USEDEFAULT,// 初始化 x 坐标
CW_USEDEFAULT,// 初始化 y 坐标
CW_USEDEFAULT,// 初始化窗口宽度
CW_USEDEFAULT,// 初始化窗口高度
NULL, // 父窗口句柄
NULL, // 窗口菜单句柄
hInstance,// 程序实例句柄
NULL); // 创建参数
// 显示窗口
ShowWindow(hWnd,SW_SHOW);
// 更新主窗口客户区
UpdateWindow(hWnd);
创建窗口完成之后,ShowWindows显示该窗口,第二个参数SW_SHOW表
示在当前位置以当前大小激活并显示由第一个参数标识的窗口。然
后,函数UpdateWindows向窗口发送一条WM_PAINT消息,以通知窗口
更新其客户区。
////////////////////////////////////////////////////////////////////////////////////////////
CreateWindow()原型:
HWNDCreateWindow(LPCTSTR lpClassName, // 指向已注册的类名
LPCTSTR lpWindowName,// 指向窗口名称
DWORDdwStyle, // 窗口样式
int x, // 窗口的水平位置
int y, // 窗口的垂直位置
int nWidth,// 窗口宽度
int nHeight,// 窗口高度
HWNDhWndParent, // 父窗口或所有者窗口句柄
HMENU hMenu,// 菜单句柄或子窗口标识符
HANDLEhInstance, // 应用程序实例句柄
LPVOIDlpParam, // 指向窗口创建数据的指针
);
////////////////////////////////////////////////////////////////////////////////////////////
在完成上面的步骤之后,进入应用程序的主消息循环。一般情况下,
主消息循环具有下面的格式:
while(GetMessage(&msg,NULL,0,0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
下面我们来看一下程序主窗口的窗口过程WndProc。窗口过程名是可
以由用户自行定义,然后在注册窗口类时在WNDCLASS结构中指定。但
是,一般来说,程序都把窗口过程命令为WndProc来类似的名称,如
MainWndProc等,并不是一定要这样做,但是这样明显的有利于阅
读,因此也是我们推荐的做法。窗口过程具有如下的原型:
LRESULTWINAPI WndProc(HWND,UINT,WPARAM,LPARAM);
或
LRESULTCALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
对于编译器而言,两种书写形式都是一样的,它们都等价于 long __stdcall WndProc(void *,unsigned int,unsigned int,long)
窗口过程使用了四个参数,在它被调用时(再强调一点,一般情况
下,窗口过程是由操作系统调用,而不是由应用程序调用的,这就是
我们为什么将它们称为回调函数的道理),这四个参数对应于所发送
消息结构的前四个成员。下面给出了一个窗口过程的例子:
// WndProc 主窗口过程
LRESULTWINAPI WndProc (HWND hWnd,
UINT msg,
WPARAMwParam,
LPARAMlParam)
{
HDC hdc;
RECT rc;
HPENhPen,hPenOld;
HBRUSHhBrush,hBrushOld;
switch (msg)
{
caseWM_PAINT:
hdc=GetDC(hWnd);
GetClientRect(hWnd,&rc);
hPen=CreatePen(PS_SOLID,0,RGB(0,0,0));
hBrush=CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,0));
hPenOld=SelectObject(hdc,hPen);
hBrushOld=SelectObject(hdc,hBrush);
Ellipse(hdc,rc.left,rc.top,rc.right,rc.bottom);
SelectObject(hdc,hPenOld);
SelectObject(hdc,hBrushOld);ReleaseDC(hWnd,hdc);
break;
caseWM_DESTROY:
PostQuitMessage(0);
break;
default:
break;
}
returnDefWindowProc(hWnd,msg,wParam,lParam);
}
在该窗口过程中,我们处理了最基本两条消息。
第一条消息是WM_PAINT,当窗口客户区的全部或一部分需要重绘时,
系统向该窗口发送该消息。在前面的过程中我们已经提到过,在使用
ShowWindow函数显示窗口之后,通常随即调用函数UpdateWindow,该
函数直接向窗口过程发送一个WM_PAINT消息,以通知窗口绘制其客户
区。在该消息的处理函数中,我们先使用GetDC获得窗口的设备句
柄,关于设备句柄本书后面将要专门涉及,这里我们只需知道它是用
来调用各种绘图方法的。然后调用GetClientRect获得当前窗口的客
户区矩形。接着调用CreatePen创建一个黑色画笔,调用
CreateHatchBrush创建一个45度交叉线的填充画刷,并且
SelectObject函数将它们选入设备描述表中,原有的画笔和画刷被保
存到hPenOld和hBrushOld中,以便以后恢复。完成以上步骤之后,调
用Ellipse函数以当前客户区大小绘制一个椭圆。最后,再一次调用
SelectObject函数恢复原有的画笔和画刷,并调用ReleaseDC释放设
备描述表句柄。在这个消息的处理代码中,我们涉及到了一些新的概
念、数据类型和API函数,然而本章并不着意于讲述这些内容,读者
也不必深究它们,这些代码只是为了完整该示例程序才使用的。对于
窗口来说,除了客户区以外的其它内容将由系统进行重绘,这些内容
包括窗口标题条、边框、菜单条、工具条以及其它控件,如果包含了
它们的话。这种重绘往往发生在覆盖于窗口上方的其它窗口被移走,
或者是窗口被移动或改变大小时。因此,对于大多数窗口过程来说,
WM_PAINT消息是必须处理的。
另一个对于绝大多数窗口过程都必须处理的消息是WM_DESTROY,当窗口被撤消时(比如用户从窗口的系统菜单中选择了“关闭”,或者单
击了右边的小叉,对于这些事件,Windows的默认处理是调用
DestroyWindow函数撤销相应的窗口),将会接收到该消息。由于本程
序仅在一个窗口,因此在这种情况下应该终止应用程序的执行,因此
我们调用了PostQuitMessage函数,该函数向线程的消息队列中放入
一个WM_QUIT消息,传递给PostQuitMessage函数的参数将成为
WM_QUIT消息的wParam参数,在上面的例子中,该值为0。
对于其它情况,在上面的示例程序中我们没有必要进行处理,
Windows专门为此提供了一个默认的窗口过程,称为DefWindowProc,
我们只需要以WndProc的参数原封不动的调用默认窗口过程
DefWindowProc,并将其返回值作为WndProc的返回值即可。
这个程序的完整代码给出如下:
#include<windows.h>
// 函数原型
int WINAPIWinMain(HINSTANCE,HINSTANCE,LPSTR,int);
LRESULTWINAPI WndProc(HWND,UINT,WPARAM,LPARAM);
// WinMain 函数
int WINAPIWinMain (HINSTANCE hInstance,
HINSTANCEhPrevInstance,
LPSTRlpCmdLine,
intnCmdShow) HWND hWnd; // 主窗口句柄
MSG msg; // 窗口消息
WNDCLASS wc;// 窗口类
if(!hPrevInstance)
{
// 填充窗口类信息
wc.style=CS_HREDRAW|CS_VREDRAW;
wc.lpfnWndProc=WndProc;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
wc.hInstance=hInstance;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=GetStockObject(WHITE_BRUSH);
wc.lpszMenuName=NULL;
wc.lpszClassName="SdkDemo1";
// 注册窗口类
RegisterClass(&wc);
}
// 创建应用程序主窗口
hWnd=CreateWindow("SdkDemo1", // 窗口类名
"第一个Win32 SDK应用程序",// 窗口标题
WS_OVERLAPPEDWINDOW,// 窗口样式
CW_USEDEFAULT,// 初始化 x 坐标
CW_USEDEFAULT,// 初始化 y 坐标
CW_USEDEFAULT,// 初始化窗口宽度 CW_USEDEFAULT, // 初始化窗口高度
NULL, // 父窗口句柄
NULL, // 窗口菜单句柄
hInstance,// 程序实例句柄
NULL); // 创建参数
// 显示窗口
ShowWindow(hWnd,SW_SHOW);
// 更新主窗口客户区
UpdateWindow(hWnd);
// 开始消息循环
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
returnmsg.wParam;
}
// WndProc 主窗口过程
LRESULTWINAPI WndProc (HWND hWnd,
UINT msg,
WPARAMwParam,
LPARAMlParam)
{
HDC hdc;
RECT rc;
HPENhPen,hPenOld; HBRUSH hBrush,hBrushOld;
switch (msg)
{
caseWM_PAINT:
hdc=GetDC(hWnd);
GetClientRect(hWnd,&rc);
hPen=CreatePen(PS_SOLID,0,RGB(0,0,0));
hBrush=CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,0));
hPenOld=SelectObject(hdc,hPen);
hBrushOld=SelectObject(hdc,hBrush);
Ellipse(hdc,rc.left,rc.top,rc.right,rc.bottom);
SelectObject(hdc,hPenOld);
SelectObject(hdc,hBrushOld);
ReleaseDC(hWnd,hdc);
break;
caseWM_DESTROY:
PostQuitMessage(0);
break;
default:
break;
}
returnDefWindowProc(hWnd,msg,wParam,lParam);
}
Windows中
所用的数
据类型
对应的基本数据
类型
说明
BOOL int 布尔值
BSTR unsigned short * 32位字符指针
BYTE unsigned char 8位无符号整数
COLORREF unsigned long 用作颜色值的32位值
DWORD unsigned long 32位无符号整数,段地址和相关的偏移地
址
LONG long 32位带符号整数
LPARAM long 作为参数传递给窗口过程或回调函数的32
位值
LPCSTR const char * 指向字符串常量的32位指针
LPSTR char * 指向字符串的32位指针
LPCTSTR const char *
(注1)
指向可移植的Unicode和DBCS字符串常量
的32位指针
LPTSTR char *(注1) 指向可移植为Unicode和DBCS字符串的32
位指针
LPVOID void * 指向未定义类型的32位指针
LRESULT long 来自窗口过程或回调函数的32位返回值
UINT unsigned int 32位无符号整数
WNDPROC long
(__stdcall *)
(void
*,unsigned
int,unsigned
int,long)(注2)
指向窗口过程的32位指针
WORD unsigned short 16位无符号整数
WPARAM unsigned int 当作参数传递给窗口过程或回调函数的32
位值
注1: 这是在DBCS版本下的情况,在Unicode版本下LPCTSTR和LPTSTR将代表
其它的数据类型。
注2: 事实上,WNDPROC被定义为LRESULT (CALLBACK*)(HWND, UINT,
WPARAM,LPARAM),这个定义最终被编译器解释为long (__stdcall *)(void
*,unsignedint,unsigned int,long)。
表3. 3 Windows公用句柄类型 (void*)
句柄类型 说明
HBITMAP 保存位图信息的内存域的句柄
HBRUSH 画刷句柄
HCTR 子窗口控件句柄
HCURSOR 鼠标光标句柄 HDC 设备描述表句柄
HDLG 对话框句柄
HFONT 字体句柄
HICON 图标句柄
HINSTANCE 应用程序的实例句柄
HMENU 菜单句柄
HMODULE 模块句柄
HPALETTE 颜色调色板句柄
HPEN 在设备上画图时用于指明线型
的笔的句柄
HRGN 剪贴区域句柄
HTASK 独立于已执行任务的句柄
HWND 窗口句柄