关于窗口类

每个窗口类都有由同一类的所有窗口共享的关联窗口过程。 窗口过程会处理该类的所有窗口的消息,因此控制其行为和外观。 有关详细信息,请参阅 窗口过程

进程必须先注册窗口类,然后才能创建该类的窗口。 注册窗口类会将窗口过程、类样式和其他类属性与类名相关联。 当进程在 CreateWindowCreateWindowEx 函数中指定类名时,系统将创建一个窗口,其中包含与该类名关联的窗口过程、样式和其他属性。

本部分讨论以下主题。

窗口类的类型

有三种类型的窗口类:

这些类型在范围和时间以及注册和销毁方式上有所不同。

系统类

系统类是系统注册的窗口类。 许多系统类可用于所有要使用的进程,而其他类则仅供系统内部使用。 由于系统注册了这些类,因此进程无法销毁它们。

系统在某个进程的线程首次调用用户或 Windows 图形设备接口(GDI)函数时,注册该进程的系统类。

每个应用程序都会收到其自己的系统类副本。 在同一 VDM 中,所有基于 16 位 Windows 的应用程序共享系统类,就像它们在 16 位 Windows 上一样。

下表描述了可供所有进程使用的系统类。

班级 DESCRIPTION
按钮 用于按钮的类。
ComboBox 用于组合框的类。
编辑 用于编辑控件的类。
ListBox 用于列表框的类。
MDIClient MDI 客户端窗口的类。
ScrollBar 用于滚动条的类。
静态的 静态控件的类。

 

下表描述了仅可供系统使用的系统类。 为了完整起见,此处列出了它们。

班级 DESCRIPTION
ComboLBox 用于组合框中所含列表框的类。
DDEMLEvent 用于动态数据交换管理库 (DDEML) 事件的类。
消息 用于纯消息窗口的类。
#32768 用于菜单的类。
#32769 用于桌面窗口的类。
#32770 用于对话框的类。
#32771 用于任务切换窗口的类。
#32772 用于图标标题的类。

 

应用程序全局类

应用程序全局类是由可执行文件或 DLL 注册的窗口类,可用于进程中所有其他模块。 例如,.dll 可以调用 RegisterClassEx 函数来注册将自定义控件定义为应用程序全局类的窗口类,以便加载 .dll 的进程可以创建自定义控件的实例。

若要创建可在每个进程中使用的类,请在 .dll 中创建窗口类并在每个进程中加载 .dll。 若要在每个进程中加载 .dll,请在以下注册表项中将其名称添加到 AppInit_DLLs 值:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows

每当进程启动时,系统就会在新启动进程的上下文中加载指定的 .dll,然后再调用其入口点函数。 .dll 必须在初始化过程中注册该类,并且必须指定 CS_GLOBALCLASS 样式。 有关详细信息,请参阅 类样式

若要删除应用程序全局类并释放与其关联的存储,请使用 UnregisterClass 函数。

应用程序本地类

应用程序本地类是指可执行文件或 .dll 为其专用而注册的任何窗口类。 虽然可以注册任意数量的本地类,但通常只注册一个。 此窗口类支持应用程序主窗口的窗口过程。

当注册它的模块关闭时,系统会销毁本地类。 应用程序还可以使用 UnregisterClass 函数删除本地类并释放与其关联的存储。

系统如何定位窗口类

系统为三种类型的窗口类分别维护着一个结构体列表。 当应用程序调用 CreateWindow 或 CreateWindowEx 函数来创建具有指定类的窗口时,系统使用以下过程来查找该类。

  1. 在应用程序本地类列表中搜索具有指定名称的类,该类的实例句柄与模块的实例句柄匹配。 (多个模块可以使用同一名称在同一进程中注册本地类。
  2. 如果名称不在应用程序本地类列表中,请搜索应用程序全局类的列表。
  3. 如果名称不在应用程序全局类列表中,请搜索系统类的列表。

应用程序创建的所有窗口都使用此过程,包括由系统代表应用程序创建的窗口,例如对话框。 可以在不影响其他应用程序的情况下重写系统类。 也就是说,应用程序可以注册与系统类同名的应用程序本地类。 这会替换应用程序上下文中的系统类,但不阻止其他应用程序使用系统类。

注册窗口类

窗口类定义窗口的属性,例如窗口的样式、图标、光标、菜单和窗口过程。 注册窗口类的第一步是使用窗口类信息填充 WNDCLASSEX 结构。 有关详细信息,请参阅 Window 类的元素。 接下来,将结构传递给 RegisterClassEx 函数。 有关详细信息,请参阅 “使用窗口类”。

若要注册应用程序全局类,请在 WNDCLASSEX 结构的样式成员中指定CS_GLOBALCLASS样式。 注册应用程序本地类时,请不要指定 CS_GLOBALCLASS 样式。

如果使用 RegisterClassExRegisterClassExA 的 ANSI 版本注册窗口类,应用程序会请求系统使用 ANSI 字符集将消息的文本参数传递给创建的类的窗口;如果使用 RegisterClassExRegisterClassExW 的 Unicode 版本注册类,则应用程序会请求系统使用 Unicode 字符集将消息的文本参数传递给所创建的类的窗口。 IsWindowUnicode 函数使应用程序能够查询每个窗口的性质。 有关 ANSI 和 Unicode 函数的详细信息,请参阅 函数原型的约定

注册该类的可执行文件或 DLL 是类的所有者。 系统会根据注册类时传递给 RegisterClassEx 函数的 WNDCLASSEX 结构的 hInstance 成员来确定类所有权。 对于 DLL,hInstance 成员必须是 .dll 实例的句柄。

当拥有该类的 .dll 已卸载时,该类不会被销毁。 因此,如果系统为该类的窗口调用窗口过程,则会导致访问冲突,因为包含该窗口过程的 .dll 不再在内存中。 在卸载 .dll 并调用 UnregisterClass 函数之前,进程必须使用该类销毁所有窗口。

Window 类的元素

窗口类的元素定义属于该类的窗口的默认行为。 注册窗口类的应用程序通过在 WNDCLASSEX 结构中设置适当的成员并将结构传递给 RegisterClassEx 函数,将元素分配给该类。 GetClassInfoExGetClassLong 函数检索有关给定窗口类的信息。 SetClassLong 函数更改应用程序已注册的本地或全局类的元素。

尽管完整的窗口类包含许多元素,但系统只要求应用程序提供类名、窗口过程地址和实例句柄。 使用其他元素定义类窗口的默认属性,例如光标的形状和窗口菜单的内容。 必须将 WNDCLASSEX 结构的任何未使用的成员初始化为零或 NULL。 窗口类元素如下表所示。

元素 目的
类名 将类与其他已注册的类区分开来。
窗口过程地址 指向一个函数的指针,该函数处理发送到该类窗口的所有消息,并定义该窗口的行为。
实例句柄 标识注册类的应用程序或 .dll。
类游标 定义系统为类窗口显示的鼠标光标。
类图标 定义大图标和小图标。
类背景画笔 定义打开或绘制窗口时填充工作区的颜色和图案。
分类菜单 指定未显式定义菜单的窗口的默认菜单。
类样式 定义如何在移动或调整窗口大小后更新窗口、如何处理鼠标双击、如何为设备上下文分配空间以及窗口的其他方面。
额外类内存 指定系统应为类保留的额外内存量(以字节为单位)。 类中的所有窗口共享额外的内存,并可用于任何应用程序定义的用途。 系统将此内存初始化为零。
额外窗口内存 指定系统应为属于该类的每个窗口保留的额外内存量(以字节为单位)。 额外的内存可用于任何应用程序定义的用途。 系统将此内存初始化为零。

 

类名

每个窗口类都需要一个 类名 来区分此类与其他类。 通过将 WNDCLASSEX 结构的 lpszClassName 成员设置为指定名称的以 null 结尾的字符串的地址来分配类名。 由于窗口类特定于进程,因此窗口类名称仅在同一进程中是唯一的。 此外,由于类名占用系统专用 Atom 表中的空间,因此应尽可能短地保留类名字符串。

GetClassName 函数检索给定窗口所属的类的名称。

窗口过程地址

每个类都需要一个窗口过程地址来定义用于处理类中窗口的所有消息的窗口过程的入口点。 当系统要求窗口执行任务时,系统会将消息传递给过程,例如绘制其工作区或响应用户输入。 一个过程通过将一个窗口过程的地址复制到 WNDCLASSEX 结构的 lpfnWndProc 成员,从而将窗口过程分配给类。 有关详细信息,请参阅 窗口过程

实例句柄

每个窗口类都需要一个实例句柄来标识注册该类的应用程序或 .dll。 系统需要实例句柄来跟踪所有模块。 系统为正在运行的可执行文件或 .dll 的每个副本分配句柄。

系统将实例句柄传递给每个可执行文件的入口点函数(请参阅 WinMain)和 .dll(请参阅 DllMain)。 可执行文件或 .dll 通过将此实例句柄复制到 WNDCLASSEX 结构的 hInstance 成员来为类分配此句柄。

类游标

在类窗口的客户区中,类光标 定义光标的形状。 当光标进入窗口的工作区时,系统会自动将光标设置为给定的形状,并确保它保留在工作区中时保持该形状。 若要将光标形状分配给窗口类,请使用 LoadCursor 函数加载预定义的游标形状,然后将返回的游标句柄分配给 WNDCLASSEX 结构的 hCursor 成员。 或者,提供自定义游标资源,并使用 LoadCursor 函数从应用程序的资源加载它。

系统不需要类游标。 如果应用程序将 WNDCLASSEX 结构的 hCursor 成员设置为 NULL,则不会定义类游标。 系统假定,每当光标进入窗口时,窗口都会设置光标的形状。 每当窗口收到WM_MOUSEMOVE消息时,窗口都可以通过调用 SetCursor 函数来设置光标形状。 有关游标的详细信息,请参阅 游标

类图标

类图标是系统用来表示特定类的窗口的图片。 应用程序可以有两个类图标-一个大图标和一个小图标。 系统在任务切换窗口中显示窗口的大类图标,当用户按下 Alt+TAB 时,该图标显示在任务栏和资源管理器的大图标视图中。 小类图标显示在窗口的标题栏和任务栏和资源管理器的小图标视图中。

若要将大图标和小图标分配给窗口类,请在 WNDCLASSEX 结构的 hIcon 和hIconSm 成员中指定图标的句柄。 图标尺寸必须符合大类图标和小类图标所需的尺寸。 对于大型类图标,可以通过在调用 GetSystemMetrics 函数时指定SM_CXICONSM_CYICON值来确定所需的维度。 对于小类图标,请指定 SM_CXSMICONSM_CYSMICON 值。 有关信息,请参阅 图标

如果应用程序将 WNDCLASSEX 结构的 hIcon 和hIconSm 成员设置为 NULL,则系统会使用默认应用程序图标作为窗口类的大小型类图标。 如果指定大型类图标而不是小型类图标,则系统会基于大型类图标创建一个小类图标。 但是,如果指定小类图标而不是大型图标,则系统会使用默认应用程序图标作为大型类图标,并将指定的图标用作小类图标。

可以使用 WM_SETICON 消息覆盖特定窗口的大或小类图标。 可以使用 WM_GETICON 消息检索当前大类或小类图标。

类背景画笔

类背景画笔通过填充窗口的客户区,来为应用程序的后续绘制做准备。 系统使用画笔用纯色或图案填充工作区,从而从该位置删除所有以前的图像,无论它们是否属于窗口。 系统通过将 WM_ERASEBKGND 消息发送到窗口,通知一个窗口应绘制其背景。 有关详细信息,请参阅 画笔

若要向类分配背景画笔,请使用相应的 GDI 函数创建画笔,并将返回的画笔句柄分配给 WNDCLASSEX 结构的 hbrBackground 成员。

可以不创建画笔,而是将应用程序的 hbrBackground 成员设置为标准系统颜色值之一。 有关标准系统颜色值的列表,请参阅 SetSysColors

若要使用标准系统颜色,应用程序必须将背景色值增加一个。 例如, COLOR_BACKGROUND + 1 是系统背景色。 或者,您可以使用 GetSysColorBrush 函数来获取一个画笔句柄,该句柄对应于标准系统颜色,然后在 WNDCLASSEX 结构的 hbrBackground 成员中指定该句柄。

系统不要求窗口类具有类背景画笔。 如果此参数设置为 NULL,则每当窗口收到 WM_ERASEBKGND 消息时,该窗口都必须绘制自己的背景。

类菜单

类菜单定义在创建窗口时没有给出显式菜单时由类中的窗口使用的默认菜单。 菜单是一个命令列表,用户可以从中选择操作由应用程序执行。

通过将 WNDCLASSEX 结构的 lpszMenuName 成员设置为指定菜单资源名称的 null 终止字符串的地址,可以将菜单分配给类。 假定该菜单是给定应用程序中的资源。 系统在需要时自动加载菜单。 如果菜单资源由整数标识,而不是按名称标识,则应用程序可以在分配值之前应用 MAKEINTRESOURCE 宏,将 lpszMenuName 成员设置为该整数。

系统不需要类菜单。 如果应用程序将 WNDCLASSEX 结构的 lpszMenuName 成员设置为 NULL,则类中的窗口没有菜单栏。 即使没有提供类菜单,应用程序在创建窗口时仍可以为窗口定义菜单栏。

如果为某个类提供了一个菜单,并且创建了该类的子窗口,则忽略该菜单。 有关详细信息,请参阅 菜单

类样式

类样式定义窗口类的其他元素。 可以使用按位 OR (|) 运算符组合两个或多个样式。 若要将样式分配给窗口类,请将样式分配给 WNDCLASSEX 结构的样式成员。 有关类样式的列表,请参阅 “窗口类样式”。

类和设备上下文

设备上下文是一组特殊的值,应用程序用于在其窗口的工作区中进行绘图。 系统需要显示上每个窗口的设备上下文,但允许系统存储和处理该设备上下文的方式有一些灵活性。

如果未显式提供任何设备上下文样式,则系统假定每个窗口都使用从系统维护的上下文池中检索的设备上下文。 在这种情况下,每个窗口必须在绘制之前检索并初始化设备上下文,在绘制之后释放设备上下文。

为了避免每次需要在窗口内绘制时都检索设备上下文,应用程序可以为窗口类指定 CS_OWNDC 样式。 此类样式指示系统创建专用设备上下文,即为类中的每个窗口分配唯一的设备上下文。 应用程序只需检索一次上下文,然后将其用于所有后续绘制。

额外类内存

系统在内部维护系统中每个窗口类的 WNDCLASSEX 结构。 当应用程序注册窗口类时,它可以指示系统向 WNDCLASSEX 结构的末尾分配和追加大量额外的内存字节。 此内存称为 额外的类内存 ,由属于该类的所有窗口共享。 使用额外的类内存来存储与类相关的任何信息。

由于从系统的本地堆分配了额外的内存,因此应用程序应谨慎使用额外的类内存。 如果请求的额外类内存量大于 40 字节, 则 RegisterClassEx 函数将失败。 如果应用程序需要 40 个以上的字节,则应分配自己的内存,并将指向内存的指针存储在额外的类内存中。

SetClassWordSetClassLong 函数将值复制到额外的类内存中。 若要从额外的类内存中检索值,请使用 GetClassWordGetClassLong 函数。 WNDCLASSEX 结构的 cbClsExtra 成员指定要分配的额外类内存量。 不使用额外类内存的应用程序必须将 cbClsExtra 成员初始化为零。

额外窗口内存

系统为每个窗口维护内部数据结构。 注册窗口类时,应用程序可以指定大量额外的内存字节,称为 额外的窗口内存。 创建类的窗口时,系统将分配指定数量的额外窗口内存并将其追加到窗口结构的末尾。 应用程序可以使用此内存来存储特定于窗口的数据。

由于从系统的本地堆分配了额外的内存,因此应用程序应谨慎使用额外的窗口内存。 如果请求的额外窗口内存量大于 40 字节, 则 RegisterClassEx 函数将失败。 如果应用程序需要 40 个以上的字节,则应分配自己的内存,并将指向内存的指针存储在额外的窗口内存中。

SetWindowLong 函数将值复制到额外的内存中。 GetWindowLong 函数从额外内存中检索值。 WNDCLASSEX 结构的 cbWndExtra 成员指定要分配的额外窗口内存量。 不使用内存的应用程序必须将 cbWndExtra 初始化为零。