每个窗口都是特定窗口类的成员。 窗口类确定单个窗口用于处理其消息的默认窗口过程。 属于同一类的所有窗口都使用相同的默认窗口过程。 例如,系统为组合框类定义窗口过程(COMBOBOX):然后,所有组合框都使用该窗口过程。
应用程序通常至少注册一个新窗口类及其关联的窗口过程。 注册类后,应用程序可以创建该类的许多窗口,所有这些窗口都使用相同的窗口过程。 由于这意味着多个源可以同时调用同一段代码,因此在从窗口过程中修改共享资源时必须小心。 有关详细信息,请参阅窗口类。
对话框的窗口过程(称为对话框过程)的结构和功能与常规窗口过程类似。 本节中引用窗口过程的所有点也适用于对话框过程。 有关详细信息,请参阅 对话框。
本部分讨论以下主题。
窗口过程的结构
窗口过程是一个具有四个参数并返回有符号值的函数。 这些参数由窗口句柄、 UINT 消息标识符和两个用 WPARAM 和 LPARAM 数据类型声明的消息参数组成。 有关详细信息,请参阅 WindowProc。
消息参数通常包含低序和高阶单词中的信息。 应用程序可以使用多个宏从消息参数中提取信息。 例如, LOWORD 宏从消息参数中提取低序单词(位 0 到 15)。 其他宏包括 HIWORD、 LOBYTE 和 HIBYTE 宏。
返回值的解释取决于特定消息。 请参阅每条消息的说明以确定相应的返回值。
由于可以递归调用窗口过程,因此必须尽量减少其使用的局部变量数。 处理单个消息时,应用程序应在窗口过程外部调用函数,以避免过度使用局部变量,这可能会导致堆栈在深度递归期间溢出。
默认窗口过程
默认窗口过程函数 DefWindowProc 定义所有窗口共享的某些基本行为。 默认窗口过程为窗口提供最少的功能。 应用程序定义的窗口过程应将它未处理的任何消息传递给 DefWindowProc 函数进行默认处理。
窗口过程子类化
当应用程序创建窗口时,系统会分配一个内存块用于存储特定于窗口的信息,包括处理窗口消息的窗口过程的地址。 当系统需要将消息传递到窗口时,它会搜索窗口特定的信息,了解窗口过程的地址,并将消息传递给该过程。
子类化 是一种技术,允许应用程序截获和处理在窗口有机会处理消息之前发送或发布到特定窗口的消息。 通过对窗口进行子类分析,应用程序可以增强、修改或监视窗口的行为。 应用程序可以子类属于系统全局类的窗口,例如编辑控件或列表框。 例如,应用程序可以将编辑控件子类化,以防止控件接受某些字符。 但是,不能将属于其他应用程序的窗口或类进行子类化。 所有子类处理都必须在同一进程中执行。
应用程序通过将窗口的原始窗口过程的地址替换为新窗口过程的地址(称为 子类过程)来子类化窗口。 此后,子类过程接收发送或发布到窗口的任何消息。
子类过程可以在收到消息时执行三个作:它可以将消息传递到原始窗口过程,修改消息并将其传递给原始窗口过程,或者处理消息,而不将其传递给原始窗口过程。 如果子类过程处理消息,那么它可以在将消息传递给原始窗口过程之前、之后或前后执行。
系统提供两种类型的子类: 实例 和 全局类。 在实例子类化中,应用程序替换窗口单个实例的过程地址。 应用程序必须使用实例子类来对现有窗口进行子类化。 在 全局子类中,应用程序替换窗口类 WNDCLASSEX 结构中的窗口过程的地址。 使用类创建的所有后续窗口都具有子类过程的地址,但该类的现有窗口不受影响。
实例子类化
应用程序使用 SetWindowLongPtr 函数对窗口的实例进行子类化。 应用程序将 GWL_WNDPROC 标志、窗口的句柄传递给子类,并将子类过程的地址传递给 SetWindowLongPtr。 子类过程可以驻留在应用程序的可执行文件或 DLL 中。
传递 GWL_WNDPROC 标志时, SetWindowLongPtr 返回窗口的原始窗口过程的地址。 应用程序必须保存此地址,在 对 CallWindowProc 函数的后续调用中使用该地址,才能将截获的消息传递到原始窗口过程。 应用程序还必须具有原始窗口过程地址,才能从窗口中删除子类。 为了删除子类,应用程序再次调用 SetWindowLongPtr ,使用 GWL_WNDPROC 标志和句柄将原始窗口过程的地址传递给窗口。
系统拥有其全局类,控件的各个方面可能会随着系统版本的变化而改变。 如果应用程序必须子类属于系统全局类的窗口,开发人员可能需要在发布新版本的系统时更新应用程序。
由于创建窗口后发生实例子类化,因此无法向窗口添加任何额外的字节。 子类窗口的应用程序应使用窗口的属性列表来存储子类窗口实例所需的任何数据。 有关详细信息,请参阅 窗口属性。
当应用程序将已子类化的窗口进行子类化时,它必须按执行子类的相反顺序删除这些子类。 如果未撤消删除顺序,则可能会发生不可恢复的系统错误。
全局子类化
若要将窗口类进行全局子类化,应用程序必须具有类窗口的句柄。 应用程序还需要句柄才能删除子类。 为了获取句柄,应用程序通常会创建要子类化的类的隐藏窗口。 获取句柄后,应用程序调用 SetClassLongPtr 函数,指定句柄、 GCL_WNDPROC 标志和子类过程的地址。 SetClassLongPtr 返回类的原始窗口过程的地址。
原始窗口过程地址在全局子类处理中使用,其方式与实例子类处理中使用的方式相同。 子类过程通过调用 CallWindowProc 将消息传递到原始窗口过程。 应用程序通过再次调用 SetClassLongPtr 从窗口类中删除子类,并指定原始窗口过程的地址、 GCL_WNDPROC 标志以及子类的窗口的句柄。 全局子类化控件类的应用程序必须在应用程序终止时删除子类;否则,可能会出现不可恢复的系统错误。
全局子类与实例子类化具有相同的限制,以及一些其他限制。 应用程序不应对类或窗口实例使用额外的字节,而无需确切地知道原始窗口过程如何使用它们。 如果应用程序必须将数据与窗口相关联,则应使用窗口属性。
窗口过程超类化
超级类化 是一种技术,允许应用程序使用现有类的基本功能以及应用程序提供的增强功能创建新的窗口类。 超级类基于名为 基类的现有窗口类。 通常,基类是系统全局窗口类,如编辑控件,但它可以是任何窗口类。
超级类有自己的窗口过程,称为超级类过程。 超级类过程可以在收到消息时执行三个作:它可以将消息传递到原始窗口过程,修改消息并将其传递给原始窗口过程,或者处理消息,而不将其传递给原始窗口过程。 如果超类过程处理消息,则它可以在将消息传递到原始窗口过程之前、之后或者同时之前和之后处理该消息。
与子类过程不同,超级类过程可以处理窗口创建消息(WM_NCCREATE、 WM_CREATE等),但它还必须将它们传递给原始基类窗口过程,以便基类窗口过程可以执行其初始化过程。
若要将窗口类超类化,应用程序首先调用 GetClassInfoEx 函数来检索基类的相关信息。 GetClassInfoEx 使用基类 的 WNDCLASSEX 结构中的值填充 WNDCLASSEX 结构。 接下来,应用程序将自己的实例句柄复制到 WNDCLASSEX 结构的 hInstance 成员中,并将超级类的名称复制到 lpszClassName 成员中。 如果基类具有菜单,应用程序必须提供具有相同菜单标识符的新菜单,并将菜单名称复制到 lpszMenuName 成员中。 如果超级类过程处理 WM_COMMAND 消息,并且不将其传递给基类的窗口过程,则菜单不需要相应的标识符。 GetClassInfoEx 不返回 WNDCLASSEX 结构的 lpszMenuName、lpszClassName 或 hInstance 成员。
应用程序还必须设置 WNDCLASSEX 结构的 lpfnWndProc 成员。 GetClassInfoEx 函数使用类的原始窗口过程的地址填充此成员。 应用程序必须保存此地址,才能将消息传递到原始窗口过程,然后将超级类过程的地址复制到 lpfnWndProc 成员中。 如有必要,应用程序可以修改 WNDCLASSEX 结构的任何其他成员。 在填充 WNDCLASSEX 结构后,应用程序通过将结构的地址传递给 RegisterClassEx 函数来注册超级类。 然后,可以使用超级类来创建窗口。
由于超级分类注册了一个新的窗口类,因此应用程序可以同时添加到额外的类字节和额外的窗口字节。 由于与实例子类或全局子类不应使用这些字节的原因相同,超级类也不得将原始额外字节用于基类或窗口。 此外,如果应用程序为类或窗口实例添加额外的字节,则必须引用相对于原始基类使用的额外字节数的额外字节。 由于基类使用的字节数可能因基类的一个版本而异,因此超级类自身额外字节的起始偏移量也可能从基类的一个版本更改为下一个版本。