再上一篇:2.1 HELLOWIN程序
上一篇:2.2 一些新概念
主页
下一篇:2.4 创建窗口
再下一篇:2.5 显示窗口
文章列表

2.3 注册窗口类

《Windows 下的 C/C++高级编程》讲述Windows环境下调用Win32 API函数程序设计

对于Windows下的“窗口”,想必大家都很熟悉,与“窗口”相关的,还有一个“窗口类”的概念。

“窗口类”与“窗口”之间的关系有点类似C++中“类”与“类的实例”之间的关系。可以基于同一个窗口类创建多个窗口,正如C++中可以基于同一个类而申明多个类的实例。但是,这两种关系之间也有不同之处。在C++中,一个类的定义完全规定了基于该类的实例的所有特性,而窗口类只规定了基于该窗口类的窗口中刻画窗口内部特征和与具体显示关系不大的一些特性,包括窗口用于响应Windows消息的窗口函数、窗口的基本显示风格、窗口所属的实例句柄、窗口类名称和窗口所使用的菜单名称等。在后面我们将看到,窗口中与具体显示关系比较密切的特性在窗口创建函数CreateWindow中加以规定。

在创建一个窗口之前,必须调用RegisterClass函数为窗口注册一个窗口类。RegisterClass函数的定义如下:

ATOM RegisterClass(const WNDCLASS *lpWndClass);

如果注册失败,该函数返回零值。如果注册成功,该函数返回一个非零值,ATOM类型用于保证其返回值在整个系统中是独一无二的,区别于其他的窗口类。

该函数仅仅使用一个参数:一个指向WINDCLASS结构体的指针。WNDCLASS结构体中包括了两个指向字符串的指针域,因此在WINYSER.H头文件中定义了两种不同的方式。

第一个是用于ASCII版的WNDCLASSA如下:

typedef struct _WNDCLASSA

{

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HINSTANCE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCSTR lpszMenuName;

LPCSTR lpszClassName;

} WNDCLASSA;

另一个是用于Unicode版的WNDCLASSW如下:

typedef struct _WNDCLASSW

{

#第17页~ Windows下的C/C++高级编程

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HINSTANCE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCWSTR pszMenuName;

LPCWSTR pszClassName;

} WNDCLASSW;

两者唯一的区别在于WNDCLASSA的最后两个域定义为指向ASCII字符串常量的指针,而WNDCLASSW的最后两个域定义为指向宽字符串常量的指针。

通过第3章所讲述的处理方式,Windows提供了一个“通用的”版本,定义如下:

typedef struct _WNDCLASS

{

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HINSTANCE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCTSTR lpszMenuName;

LPCTSTR lpszClassName;

} WNDCLASS;

通过将最后两个域改为LPCTSTR,这样就实现了ASCII版本和Unicode版本的统一。

这里有必要讲述一下WNDCLASS结构体中所出现的新的前缀。这些前缀同样遵循匈牙利表示法。其中,lpfn前缀表示“long pointer to function”(即“指向函数的长指针”);cb前缀表示“count of bytes”(即“字节数”),常用于表示字节长度的变量;h前缀表示变量为一个句柄,这里hbr前缀则表示“handle of a brush”即“一个刷子的句柄”);lpsz前缀表示“longpointer to a string ended with zero”(即“指向以0结尾的字符串的长指针”)。

为调用RegisterClass函数,需要先定义一个WNDCLASS结构,然后初始化该结构的10个域,作为参数传给RegisterClass。

在WNDCLASS结构中最重要的两个域是第二个和最后一个。第二个域(lpfnWndProc)是所有基于该窗口类创建的窗口所使用的窗口函数的入口地址,正是这个窗口函数负责处理窗口所响应的所有信息。最后一个域是该窗口类的名称,在后面CreateWindow 函数中还将使用,正是通过这个域将“窗口”与其响应的“窗口类”联系起来。

下面依次来具体看一看WNDCLASS结构中的10个域。

#第18页~

第2章 窗口和消息

语句

wndclass.sytle = CS_HREDRAW | CS_VREDRAW;

使用C语言的“位或”操作符组合了CS_HREDRAW和CS_VREDRAW两种“类风格”标识符。在WINUSER.H中定义了一些以CS为前缀的标识符如下:

#define CS_VREDRAW 0x0001

#define CS_HREDRAW 0x0002

#define CS_DBLCLKS 0x0008

#define CS_OWNDC 0x0020

#define CS_CLASSDC 0x0040

#define CS_PARENTDC 0x0080

#define CS_NOCLOSE 0x0200

#define CS_SAVEBITS 0x0800

#define CS_BYTEALIGNCLIENT 0x1000

#define CS_BYTEALIGNWINDOW 0x2000

#define CS_GLOBALCLASS 0x4000

#define CS_IME 0x00010000

其中,每一个标识符都在复合值当中设置了一位,一般称这种标识符为“位标识符”,通常只用到其中的一小部分。HelloWin中用到的两个标识符CS_HREDRAW和CS_VREDRAW分别表示:所有基于该窗口类创建的窗口,每当窗口的水平方向的大小或者垂直方向的大小改变时,要完全刷新窗口。试着改变窗口的大小,可以看到“Hello Windows 98!”始终显示在窗口的中央。这正是由这两个位标识符所指定的。

语句

wndclass.lpfnWndProc = WndProc;

将该窗口类的窗口函数设置为WndProc。这个函数将处理基于这个窗口类所创建的窗口所响应的全部消息。也许读者会感到奇怪,等号的左边是一个指向函数的指针,而等号的右边是一个函数名,怎么可以这样赋值?其实,在C语言中,函数名正是处理为函数指针来实现的。在这里,等号的右边也是一个函数指针。

窗口函数是“窗口类”以及基于窗口类的所有“窗口”最重要的特性之一,在本章的后面我们将详细讲解窗口函数。

下面的两个域用于在类结构和Windows内部保存的窗口结构中预留一些额外的空间:

wndclass.cbClsExtra = 0;

wndclass.cbEndExtra = 0;

应用程序可以根据需要来定义和使用预留的空间。这里我们并没有使用它们,所以将其赋值为0。否则,正如其匈牙利表示法前缀所表明的,这两个域将被赋值为所需预留的“字节数”。

语句

wndclass.hInstance = hInstance;

指定所在程序的实例句柄。

语句

wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

用于为所有基于该窗口类创建的窗口设置一个图标。图标就是平时出现在窗口标题栏的最左

#第19页~ Windows下的C/C++高级编程

边、当窗口最小化时出现在Windows任务栏中的一个小的位图图像。以后我们将讲解如何为Windows程序定制特定的图标,现在先暂时使用Windows预定义的图标。

LoadIcon函数的定义如下:

HICON LoadIcon( HINSTANCE hInstance, LPCTSTR lpIconName );

如果要使用自己定制的图标,第一个参数应当设置为当前程序的实例句柄hInstance;第二个参数是程序中所包含的、已经设计好的图标的名称。

如果使用Windows预定义的图标,第一个参数应当设置为NULL。第二个参数为以IDI为前缀的图标标识符。这些标识符在WINUSER.H中定义,其中定义了应用程序、疑问、错误、警告和信息等各种缺省图标。这里所调用的IDI_APPLICATION图标是一个简单的应用程序窗口小图形。

该函数返回一个hIcon,即“handle to an icon”(“图标句柄”)。

语句

wndclass.hCursor = LoadCursor( NULL, IDC_ARROW );

与前一条语句十分相似。LoadCursor函数为所有基于该窗口类的窗口设置鼠标形状。其定义如下:

HCURSOR LoadCursor( HINSTANCE hInstance, LPCTSTR lpCursorName );

用法与LoadIcon也十分相似,只不过第二个参数是关于鼠标图形的。

这里所调用的是Windows预定义的IDC_ARROW(“普通箭头”形状)。当鼠标移动到基于该窗口类创建的窗口的客户区中时,它将显示为一个普通的鼠标箭头。

语句

wndclass.hbrBackground = GetStockObject( WHITE_BRUSH );

指定了基于该窗口类创建的窗口的背景色。GetStockObject函数的定义如下:

HGDIOBJ GetStockObject( int fnObject );

前面已经提到过,hbr前缀表示“handle to a brush”(即“刷子句柄”)。“刷子”是一个Windows GDI(Graphical Device Interface:图形设备接口)术语,指用来给一个封闭区域填充颜色的一种模式。关于GDI我们将在第五章中详细介绍。这里只需要知道GetStockObject函数返回一个可以作为刷子句柄的值即可。该函数所需的参数是Windows预定义的几个标准刷子(也称为备用“stock”刷子)之一。这些预定义刷子在MSDN/Platform SDK/Graphics andMultimedia Services/Windows GDI/Devices Contexts Reference/Devices ContextFunctions/GetStockObject中有详细的列表。

这里的GetStockObject函数调用将返回一个白色刷子(由WHITE_BRUSH)的句柄。这意味着基于该窗口类所创建的窗口客户区域的背景完全为白色,通常情况下都是如此。

语句

wndclass.lpszMenuName = NULL;

用于指定窗口类的菜单。由于HelloWin没有使用菜单,所以该字段被置为NULL,否则应当设置该字段为所用菜单的名称(字符串)。

最后,语句

wndclass.lpszClassName = szClassName;

用于给该窗口类指定一个类名。请注意,这也是窗口类中一个极其重要的域,后面CreateWindow函数还将使用它。系统正是通过“窗口类”的类名将窗口类和基于该窗口类创 #第20页~

第2章 窗口和消息建的“窗口”联系起来。

在申明了一个窗口类并初始化该类的10个域之后,即可调用RegisterClass函数来注册这个窗口类。该函数唯一的参数是一个指向WNDCLASS结构的指针。

实际上,根据是否定义了Unicode,RegisterClass函数在内部执行的时候将有不同的实现。如果并未定义Unicode,则调用RegisterClassA函数,并以一个指向WNDCLASSA结构的指针作为参数;如果定义了Unicode,则调用RegisterClassW函数,并以一个指向WNDCLASSW结构的指针作为参数。

这里就有一个问题,如果用定义了的 UNICODE 标识符编译程序,程序将调用RegisterClassW。该程序在 Windows NT 系列操作系统中可以很好地运行。但是,如果在Windows 98上运行,则由于系统对Unicode支持有限,RegisterClassW函数将不能正常工作,它通过返回一个0值表明有错误发生。因此,我们必须对该函数的调用情况加以检查。以下是HelloWin中的实现代码:

if ( !RegisterClass( &wndclass ) )

{

MessageBox( NULL, TEXT(“This program requires Windows NT!”),

szAppName, MB_ICONERROR );

return 0;

}

前面已经提到过,MessageBoxW是少数几个可以在Windows 98环境下运行的Unicode函数之一,所以它能够正常运行。