C语言实例教程(PDF格式)-第17部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
APIENTRY等。它们对编译器而言都是一回事,最终将被解释为
__stdcall。在Windows环境下编程,会遇到很多这样的情况,注意不
要混淆它们。
一般情况下,我们应该在WinMain函数中完成下面的操作:
1。 注册窗口类;
2。 创建应用程序主窗口;
…………………………………………………………Page 129……………………………………………………………
3。 进入应用程序消息循环。
接下来我们将依次讨论这些内容。
在Windows应用程序中,每一个窗口都必须从属于一个窗口类,窗口
类定义了窗口所具有的属性,如它的样式、图标、鼠标指针、菜单名
称及窗口过程名等。在注册窗口类前,我们先创建一个类型为
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);
下面解释一下结构WNDCLASS 中各成员的含义:
style: 指定窗口样式。该样式可以为一系列屏
蔽位的按位或,在前面的例子中,CS_HREDRAW表
示当窗口用户区宽度改变时重绘整个窗口,而
CS_VREDRAW则表则表示当窗口用户区高度改变时
重绘整个窗口。对于其它的窗口样式,请参阅
SDK中关于WNDCLASS的联机文档。(顺便说一句,
请注意该成员的大小写,它是小写的style,而
…………………………………………………………Page 130……………………………………………………………
不是Style。)
lpfnWndProc: 指向窗口过程的指针。关于窗口
过程我们将以后面的内容中讲述。在前面的例子
中,我们使用名为WndProc的窗口过程。
cbClsExtra: 指定在窗口类结构之后分配的附加
字节数。操作系统将这些字节初始化为0。
cbWndExtra: 指定在窗口实例之后分配的附加字
节数。操作系统将这些字节初始化为0。如果应
用程序使用WNDCLASS结构注册一个使用资源文件
中的CLASS指令创建的对话框,那么cbWndExtra
必须被设置为DLGWINDOWEXTRA。
hInstance : 标识该类的窗口过程所属的实例。
hIcon : 标识类图标。该成员必须为一个图标资
源的句柄。如果该成员为NULL,则应用程序必须
在用户最小化应用程序窗口时绘制图标。
hCursor : 标识类鼠标指针。该成员必须为一个
光标资源的句柄,如果该成员为NULL,当鼠标移
进应用程序窗口时应用程序必须显式指定指针形
状。
hbrBackground: 标识类背景刷子。该成员可以
为一个用来绘制背景的画刷句柄,或者为标准系
统颜色值之一。
lpszMenuName: 指向一個以NULL结尾的字符串的
指针,该字符串指定了类菜单的资源名称。如果
在资源名称为的菜单为一个整数所标识,则可以
使用MAKEINTRESOURCE宏将其转换为一个字符
串;如果该成员为NULL,则属于该类的窗口无默
认菜单。
lpszClassName: 指向一个以NULL结尾的字符串
或为一个原子。如果该参数为一个原子,那么它
必须是一个使用GlobalAddAtom函数创建的全局
原子;如果为一个字符串,该字符器将成员窗口
类名。
…………………………………………………………Page 131……………………………………………………………
l 注意:
l 这里多次提到窗口类这一名词,但是它和前面常说的C++类没有任
何联 系。窗口类只表示了窗口的类型,它完全不是面向对象意义
上的类,因为它不支持面向对象技术中的继承及多态等。
在使用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);
由于上述代码均加上了详尽的注释,这里仅作一些简单的说明和强
调。CreateWindow函数的原型是这样的:
HWND CreateWindow(LPCTSTR lpClassName; // 指向已注册的类名
LPCTSTR lpWindowName; // 指向窗口名称
DWORD dwStyle; // 窗口样式
…………………………………………………………Page 132……………………………………………………………
int x; // 窗口的水平位置
int y; // 窗口的垂直位置
int nWidth; // 窗口宽度
int nHeight; // 窗口高度
HWND hWndParent; // 父窗口或所有者窗口句柄
HMENU hMenu; // 菜单句柄或子窗口标识符
HANDLE hInstance; // 应用程序实例句柄
LPVOID lpParam; // 指向窗口创建数据的指针
);
在前面的示例中,我们对x、y、nWidth和nHeight参数都传递了同一
个值CW_USEDEFAULT,表示使用系统默认的窗口位置和大小,该常量
仅对于重叠式窗口 (即在dwStype样式中指定了
WS_OVERLAPPEDWINDOW,另一个常量WS_TILEDWINDOW有着相同的值)有
效。对于CreateWindows函数的其它内容,比如关于dwStyle参数所用
常量的详细参考请 自行参阅Win32 SDK中的文档。
l 注意:
l 尽管Windows 95是一个32位的操作系统,但是,其中也保留了很
多16位的特征,比如说,在Windows 95环境下,系统最多只可以
有16384个窗口句柄。而在Windows NT下则无此限。然而,事实
上,对于一般的桌面个人机系统来说,我们几乎不可能超过这个
限制。
创建窗口完成之后,ShowWindows显示该窗口,第二个参数SW_SHOW表
示在当前位置以当前大小激活并显示由第一个参数标识的窗口。然
后,函数UpdateWindows向窗口发送一条WM_PAINT消息,以通知窗口
更新其客户区。需要注意的是,由UpdateWindows发送的WM_PAINT消
息将直接发送到窗口过程 (在上面的例子中是WndProc函数),而不是
发送到进程的消息队列,因此,尽管这时应用程序的主消息循环尚未
启动,但是窗口过程仍可接收到该WM_PAINT消息并更新其用户区。
在完成上面的步骤之后,进入应用程序的主消息循环。一般情况下,
主消息循环具有下面的格式:
while (GetMessage(&msg;NULL;0;0))
…………………………………………………………Page 133……………………………………………………………
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
主消息循环由对三个API函数的调用和一个while结构组成。其中
GetMessage从调用线程的消息队列中获取消息,并将消息放到由第一
个参数指定的消息结构中。如果指定了第二个参数,则GetMessage获
取属于该参数指定的窗口句柄所标识的窗口的消息,如果该参数为
NULL,则GetMessage获取属于调用线程及属于该线程的所有窗口的消
息。最后两个参数指定了GetMessage所获取消息的范围,如果两个参
数均为0,则GetMessage检索并获取所有可以得到的消息。
在上面的代码中,变量msg是一个类型为MSG的结构对象,该结构体的
定义如下:
typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt; } MSG;
下面解释各成员的含义:
hwnd: 标识获得该消息的窗口进程的窗口句柄。
message: 指定消息值。
wParam: 其含义特定于具体的消息类型。
lParam: 其含义特定于具体的消息类型。
time: 指定消息发送时的时间。
pt: 以屏幕坐标表示的消息发送时的鼠标指针的位置。
…………………………………………………………Page 134……………………………………………………………
在while循环体中的TranslateMessage函数将虚拟按键消息翻译为字
符消息,然后将消息发送到调用线程的消息队列,在下一次调用
GetMessage函数或PeekMessage函数时,该字符消息将被获取。
TranslateMessage函数将WM_KEYDOWN和WM_KEYUP虚拟按键组合翻译为
WM_CHAR和WM_DEADCHAR,将WM_SYSKEYDOWN和WM_SYSKEYUP虚拟按键组
合翻译为WM_SYSCHAR和WM_SYSREADCHAR。需要注意的一点是,仅当相
应的虚拟按键组合能够被翻译为所对应的ASCII字符时,
TranslateMessage才发送相应的WM_CHAR消息。
如果一个字符消息被发送到调用线程的消息队列,则
TranlateMessage返回非零值,否则返回零值。
l 注意:
l 与在Windows 95操作系统下不同,在Windows NT下,
TranslateMessage对于功能键和光标箭头键也返回一个非零值。
然后,函数DispatchMessage将属于某一窗口的消息发送该窗口的窗
口过程。这个窗口由MSG结构中的hwnd成员所标识的。函数的返回值
为窗口过程的返回值,但是,我们一般不使用这个返回值。这里要注
意的是,并不一定是所有属于某一个窗口的消息都发送给窗口的窗口
过程,比如对于WM_TIMER消息,如果其lParam参数不为NULL的话,由
该参数所指定的函数将被调用,而不是窗口过程。
如果GetMessage从消息队列中得到一个WM_QUIT消息,则它将返回一
个假值,从而退出消息循环,WM_QUIT消息的wParam参数指定了由
PostQuitMessage函数给出的退出码,一般情况下,WinMain函数返回
同一值。
下面我们来看一下程序主窗口的窗口过程WndProc。窗口过程名是可
以由用户自行定义,然后在注册窗口类时在WNDCLASS结构中指定。但
是,一般来说,程序都把窗口过程命令为WndProc来类似的名称,如
MainWndProc等,并不是一定要这样做,但是这样明显的有利于阅
读,因此也是我们推荐的做法。窗口过程具有如下的原型:
LRESULT WINAPI WndProc(HWND;UINT;WPARAM;LPARAM);
或
LRESULT CALLBACK WndProc(HWND;UINT;WPARAM;LPARAM);
对于编译器而言,两种书写形式都是一样的,它们都等价于
…………………………………………………………Page 135……………………………………………………………
long __stdcall WndProc(void *;unsigned int;unsigned int;long)
窗口过程使用了四个参数,在它被调用时(再强调一点,一般情况
下,窗口过程是由操作系统调用,而不是由应用程序调用的,这就是
我们为什么将它们称为回调函数的道理),这四个参数对应于所发送
消息结构的前四个成员。下面给出了一个窗口过程的例子:
// WndProc 主窗口过程
LRESULT WINAPI WndProc (HWND hWnd;
UINT msg;
WPARAM wParam;
LPARAM lParam)
{
HDC hdc;
RECT rc;
HPEN hPen;hPenOld;
HBRUSH hBrush;hBrushOld;
switch (msg)
{
case WM_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);
…………………………………………………………Page 136……………………………………………………………
ReleaseDC(hWnd;hdc);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default: