关于窗口类

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

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

本部分讨论以下主题。

窗口类的类型

有三种类型的窗口类:

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

系统类

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

系统首次在其线程之一调用 User 或 Windows Graphics Device Interface (GDI) 函数时注册进程的系统类。

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

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

说明
Button 按钮的类。
ComboBox 组合框的类。
编辑 编辑控件的类。
ListBox 列表框的类。
MDIClient MDI 客户端窗口的类。
ScrollBar 滚动条的类。
静态 静态控件的类。

 

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

说明
ComboLBox 组合框中包含的列表框的类。
DDEMLEvent 动态数据Exchange管理库的类 (DDEML) 事件。
消息 仅消息窗口的类。
#32768 菜单的类。
#32769 桌面窗口的类。
#32770 对话框的类。
#32771 任务切换窗口的类。
#32772 图标标题的类。

 

应用程序全局类

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

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

\ HKEY_LOCAL_MACHINE软件\微软\\ Windows NT CurrentVersion\Windows

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

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

应用程序本地类

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

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

系统如何定位窗口类

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

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

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

注册 Window 类

窗口类定义窗口的属性,例如窗口的样式、图标、光标、菜单和窗口过程。 注册窗口类的第一步是使用窗口类信息填充 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 结构的 hIconhIconSm 成员中指定图标的句柄。 图标尺寸必须符合大类图标和小类图标的必需尺寸。 对于大型类图标,可以通过在调用 GetSystemMetrics 函数时指定SM_CXICONSM_CYICON值来确定所需的维度。 对于小型类图标,请指定 SM_CXSMICONSM_CYSMICON 值。 有关信息,请参阅 图标

如果应用程序将 WNDCLASSEX 结构的 hIconhIconSm 成员设置为 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,则类中的窗口没有菜单栏。 即使没有提供类菜单,应用程序在创建窗口时仍可以为窗口定义菜单栏。

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

类样式

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

类和设备上下文

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

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

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

额外的类内存

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

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

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

额外窗口内存

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

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

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