关于窗口类
每个窗口类都有一个由同一类的所有窗口共享的关联窗口过程。 窗口过程处理该类的所有窗口的消息,因此控制其行为和外观。 有关详细信息,请参阅 窗口过程。
进程必须先注册窗口类,然后才能创建该类的窗口。 注册窗口类会将窗口过程、类样式和其他类属性与类名相关联。 当进程在 CreateWindow 或 CreateWindowEx 函数中指定类名时,系统将创建一个窗口,其中包含与该类名关联的窗口过程、样式和其他属性。
本部分讨论以下主题。
窗口类的类型
有三种类型的窗口类:
这些类型在范围、注册和销毁时间和销毁方式方面有所不同。
系统类
系统类是由系统注册的窗口类。 许多系统类可供所有进程使用,而其他系统类仅供系统内部使用。 由于系统注册这些类,因此进程无法销毁它们。
当某个进程的线程首次调用用户或 Windows 图形设备接口时,系统会为进程注册系统类, (GDI) 函数。
每个应用程序都接收自己的系统类副本。 同一 VDM 中所有基于 Windows 的 16 位应用程序共享系统类,就像在 16 位 Windows 上一样。
下表描述了可供所有进程使用的系统类。
类 | 说明 |
---|---|
Button | 按钮的 类。 |
ComboBox | 组合框的 类。 |
编辑 | 编辑控件的类。 |
ListBox | 列表框的类。 |
MDIClient | MDI 客户端窗口的类。 |
ScrollBar | 滚动条的 类。 |
静态 | 静态控件的 类。 |
下表描述了仅供系统使用的系统类。 出于完整性考虑,此处列出了它们。
类 | 说明 |
---|---|
ComboLBox | 包含在组合框中的列表框的类。 |
DDEMLEvent | 动态数据交换管理库的类 (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注册以独占使用的任何窗口类。 尽管可以注册任意数量的本地类,但通常只注册一个。 此窗口类支持应用程序的main窗口的窗口过程。
注册它的模块关闭时,系统会销毁本地类。 应用程序还可以使用 UnregisterClass 函数删除本地类并释放与其关联的存储。
系统如何定位窗口类
系统维护三种类型的窗口类的结构列表。 当应用程序调用 CreateWindow 或 CreateWindowEx 函数来创建具有指定类的窗口时,系统将使用以下过程来查找类。
- 在应用程序本地类列表中搜索具有指定名称的类,该类的实例句柄与模块的实例句柄匹配。 (多个模块可以使用同一名称在同一个 process 中注册本地类。)
- 如果名称不在应用程序本地类列表中,请搜索应用程序全局类的列表。
- 如果名称不在应用程序全局类列表中,请搜索系统类的列表。
应用程序创建的所有窗口都使用此过程,包括系统代表应用程序创建的窗口,例如对话框。 可以在不影响其他应用程序的情况下替代系统类。 也就是说,应用程序可以注册与系统类同名的应用程序本地类。 这会替换应用程序上下文中的系统类,但不阻止其他应用程序使用系统类。
注册 Window 类
window 类定义窗口的属性,例如其样式、图标、光标、菜单和窗口过程。 注册窗口类的第一步是使用窗口类信息填充 WNDCLASSEX 结构。 有关详细信息,请参阅 Window 类的元素。 接下来,将结构传递给 RegisterClassEx 函数。 有关详细信息,请参阅 使用窗口类。
若要注册应用程序全局类,请在 WNDCLASSEX 结构的样式成员中指定CS_GLOBALCLASS样式。 注册应用程序本地类时,不要指定 CS_GLOBALCLASS 样式。
如果使用 AnSI 版本的 RegisterClassEx、RegisterClassExA 注册窗口类,应用程序会请求系统使用 ANSI 字符集将消息的文本参数传递到所创建类的窗口;如果使用 RegisterClassEx 的 Unicode 版本 RegisterClassExW 注册类,则应用程序会请求系统使用 Unicode 字符集将消息的文本参数传递到所创建类的窗口。 IsWindowUnicode 函数使应用程序能够查询每个窗口的性质。 有关 ANSI 和 Unicode 函数的详细信息,请参阅 函数原型的约定。
注册类的可执行文件或 DLL 是类的所有者。 注册类时,系统从传递给 RegisterClassEx 函数的 WNDCLASSEX 结构的 hInstance 成员确定类所有权。 对于 DLL, hInstance 成员必须是.dll实例的句柄。
卸载拥有类的.dll时,不会销毁该类。 因此,如果系统为该类的窗口调用窗口过程,将导致访问冲突,因为包含窗口过程的.dll不再位于内存中。 在卸载.dll并调用 UnregisterClass 函数之前,进程必须使用 类销毁所有窗口。
Window 类的元素
窗口类的元素定义属于该类的窗口的默认行为。 注册窗口类的应用程序通过在 WNDCLASSEX 结构中设置适当的成员并将结构传递给 RegisterClassEx 函数,将元素分配给类。 GetClassInfoEx 和 GetClassLong 函数检索有关给定窗口类的信息。 SetClassLong 函数更改应用程序已注册的本地类或全局类的元素。
尽管完整的窗口类由许多元素组成,但系统只需要应用程序提供类名、窗口过程地址和实例句柄。 使用其他元素定义 类窗口的默认属性,例如光标的形状和窗口菜单的内容。 必须将 WNDCLASSEX 结构的任何未使用成员初始化为零或 NULL。 窗口类元素如下表所示。
元素 | 目标 |
---|---|
类名 | 将 类与其他已注册的类区分开来。 |
窗口过程地址 | 指向函数的指针,该函数处理发送到 类中窗口的所有消息,并定义窗口的行为。 |
实例句柄 | 标识注册类的应用程序或.dll。 |
类光标 | 定义系统为 类的窗口显示的鼠标光标。 |
类图标 | 定义大图标和小图标。 |
类背景画笔 | 定义在打开或绘制窗口时填充工作区的颜色和图案。 |
类菜单 | 指定未显式定义菜单的窗口的默认菜单。 |
类样式 | 定义如何在移动或调整窗口大小后更新窗口、如何处理双击鼠标、如何为设备上下文分配空间以及窗口的其他方面。 |
额外类内存 | 指定系统应为类保留的额外内存量(以字节为单位)。 类中的所有窗口共享额外的内存,并且可以将其用于任何应用程序定义的用途。 系统会将此内存初始化为零。 |
额外窗口内存 | 指定系统应为属于 类的每个窗口保留的额外内存量(以字节为单位)。 额外的内存可用于任何应用程序定义的用途。 系统会将此内存初始化为零。 |
类名
每个窗口类都需要一个 类名称 来区分一个类。 通过将 WNDCLASSEX 结构的 lpszClassName 成员设置为指定名称的以 null 结尾的字符串的地址来分配类名。 由于窗口类特定于进程,因此窗口类名称仅在同一进程中是唯一的。 此外,由于类名在系统的专用原子表中占用空间,因此应尽可能短地保留类名字符串。
GetClassName 函数检索给定窗口所属的类的名称。
窗口过程地址
每个类都需要一个 window-procedure 地址来定义用于处理类中窗口的所有消息的窗口过程的入口点。 当系统要求窗口执行任务(例如绘制其工作区或响应用户输入)时,系统会将消息传递给过程。 进程通过将窗口过程地址复制到 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_CXICON和SM_CYICON值来确定所需的维度。 对于小类图标,请指定 SM_CXSMICON 和 SM_CYSMICON 值。 有关信息,请参阅 图标。
如果应用程序将 WNDCLASSEX 结构的 hIcon 和 hIconSm 成员设置为 NULL,则系统会使用默认应用程序图标作为窗口类的大类和小类图标。 如果指定大型类图标而不是小类图标,系统会基于大类图标创建一个小类图标。 但是,如果指定小类图标而不是大类图标,系统会使用默认应用程序图标作为大型类图标,将指定的图标用作小类图标。
可以使用 WM_SETICON 消息替代特定窗口的大类或小类图标。 可以使用 WM_GETICON 消息检索当前大类或小类图标。
类背景画笔
类背景画笔准备窗口的工作区,供应用程序进行后续绘制。 系统使用画笔用纯色或图案填充工作区,从而从该位置删除所有以前的图像,无论它们是否属于窗口。 系统通过向窗口发送 WM_ERASEBKGND 消息来通知窗口应绘制其背景。 有关详细信息,请参阅 画笔。
若要将背景画笔分配给类,请使用适当的 GDI 函数创建画笔,并将返回的画笔句柄分配给 WNDCLASSEX 结构的 hbrBackground 成员。
应用程序可以将 hbrBackground 成员设置为标准系统颜色值之一,而不是创建画笔。 有关标准系统颜色值的列表,请参阅 SetSysColors。
若要使用标准系统颜色,应用程序必须将背景色值增加 1。 例如, 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 个字节,它应分配自己的内存,并将指向内存的指针存储在额外的类内存中。
SetClassWord 和 SetClassLong 函数将值复制到额外的类内存。 若要从额外的类内存中检索值,请使用 GetClassWord 和 GetClassLong 函数。 WNDCLASSEX 结构的 cbClsExtra 成员指定要分配的额外类内存量。 不使用额外类内存的应用程序必须将 cbClsExtra 成员初始化为零。
额外窗口内存
系统为每个窗口维护一个内部数据结构。 注册窗口类时,应用程序可以指定一些额外的内存字节,称为 额外窗口内存。 创建 类的窗口时,系统会分配指定的额外窗口内存量并将其追加到窗口结构的末尾。 应用程序可以使用此内存来存储特定于窗口的数据。
由于额外的内存是从系统的本地堆分配的,因此应用程序应谨慎使用额外的窗口内存。 如果请求的额外窗口内存量大于 40 字节, 则 RegisterClassEx 函数将失败。 如果应用程序需要超过 40 个字节,它应分配自己的内存,并将指向内存的指针存储在额外的窗口内存中。
SetWindowLong 函数将值复制到额外的内存中。 GetWindowLong 函数从额外内存中检索值。 WNDCLASSEX 结构的 cbWndExtra 成员指定要分配的额外窗口内存量。 不使用内存的应用程序必须将 cbWndExtra 初始化为零。