本节内容主要介绍 Windows 窗口创建 的第一步,如何注册窗口类。在 Windows 操作操作系统中,窗口类被定义为一个结构体类型,其主要的作用是描述一组窗口的公有行为,是多个窗口共性的一种抽象。

举个简单的例子,当点击一组按钮的时候,每个按钮都有相似的反馈,而这些相同的反馈行为都是定义在窗口类中。当然,按钮也不是完全一样,每个按钮的位置、按钮上的文本都可能有所不同,而这些按钮间不同的数据被称为按钮的实例数据(instance data)。

每个窗口都必须和窗口类关联,即使你的程序仅仅只有一个窗口,仍然需要在创建窗口的时候关联一个已注册的窗口类。虽然窗口类和窗口的关系和 C++ 中的类和对象的关系类似,但是它们并不相同。窗口类仅仅是操作系统内部的一种数据结构而已。

在操作系统运行期间,每次注册窗口类都需要先填充一个叫做 WNDCLASSEX 的结构体。

在早期 Windows 中还定义了一个叫做 WNDCLASS 的结构体,其用途和 WNDCLASSEX 是相同的,只不过 WNDCLASSEX 结构体多出两个成员变量。如果你使用的是 EX 版本,请使用 RegisterClassEx 函数注册窗体类,否则请使用 RegisterClass 注册。

下面是填充窗口类的代码:

// Register the window class.
const wchar_t CLASS_NAME[]  = L"Sample Window Class";

WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = CLASS_NAME;
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

WNDCLASSEX 成员比较多,这里简单的做一下介绍,具体信息可以查看相关 MSDN 文档

  • cbSize 用来指定结构体的大小,直接使用 sizeof(WNDCLASSEX) 赋值。

  • style 用来指定窗口类的样式,具体可以查看这篇文章 Class Styles 了解,这里使用 CS_HREDRAW | CS_VREDRAW 组合样式,代表当窗口改变大小时进行重绘操作。

  • lpfnWndProc 用来指定 窗口过程函数 指针。该函数定义了窗口大多数的行为,具体可以查看 WindowProc。

  • cbClsExtra 用来定义窗口类结果体的扩展数据大小,一般填充0。

  • cbWndExtra 用来定义窗口实例的扩展数据大小,一般填充0。

  • hInstance 代表应用程序的实例句柄。该值就是 WinMain 函数 的 hInstance 参数。

  • hIcon 代表窗口类的图标句柄,这里使用默认的应用程序图标。

  • hCursor 代表窗口类的光标句柄,这里使用默认的箭头图标。

  • hbrBackground 代表窗口类背景颜色的画刷句柄,这里使用纯色的白色画刷。

  • lpszMenuName 代表窗口类的菜单句柄,这里没有菜单,填 NULL。

  • lpszClassName 是一个字符串,用来标识一个窗口类。

  • hIconSm 代表窗口类的小图标句柄,这里和 hIcon 指定相同的图标。

窗口类的名称(lpszClassName)在进程内必须唯一,不可以重名。需要注意 Windows 标准控件一样具有类名,如果你是用了这些控件,请避免与其重名,否则会导致窗口类注册失败的情况。

上述结构体中,主要的成员其实只有四个:cbSize、lpfnWndProc、hInstance 和 lpszClassName,其它的值都可以临时设置为 0。

填充 WNDCLASSEX 结构体后,需要将其注册通知操作系统,具体使用下面的函数:

ATOM WINAPI RegisterClassEx(
  _In_ const WNDCLASSEX *lpwcx
);

函数接收一个窗口类的指针,如果成功会返回一个窗口类的句柄,如果失败则会返回0。