为控件创建子类

如果一个控件几乎可以实现想要的所有功能,但还需要更多的功能,则可以通过将其子类化来更改或添加原始控件的功能。 子类可以拥有现有类的所有功能,也可以拥有要赋予它的任何其他功能。

本文档讨论如何创建子类,包括以下主题。

ComCtl32.dll 版本 6 之前的子类化控件

可以将控件放在子类中,并在控件中存储用户数据。 在使用 ComCtl32.dll 第 6 版之前的版本时就可以这样执行操作。 使用早期版本的 ComCtl32.dll 来创建子类存在一些缺点。

要创建一个新控件,最好从 Windows 常用控件之一开始,然后再对其进行扩展,以满足特定的需求。 要扩展控件,请创建一个控件,并使用新窗口过程来替换其现有窗口过程。 新过程会截获控件的信息,并对其进行处理或将其传递给原过程进行默认处理。 使用 SetWindowLongSetWindowLongPtr 函数来替换控件的 WNDPROC。 以下代码示例显示了如何替换 WNDPROC。

OldWndProc = (WNDPROC)SetWindowLongPtr (hButton,
GWLP_WNDPROC, (LONG_PTR)NewWndProc);

存储用户数据

你可能想在单个窗口中存储用户数据。 新窗口过程可以使用这些数据来确定如何绘制控件或向何处发送某些消息。 例如,可以使用数据来存储一个指向表示控件的类的 C++ 类指针。 以下示例代码显示了如何使用 SetProp 通过一个窗口来存储数据。

SetProp (hwnd, TEXT("MyData"), (HANDLE)pMyData);

旧子类化方法的缺点

下表列出了使用前述方法对控件进行子类化的一些缺点。

  • 窗口过程只能被替换一次。
  • 子类在创建后很难删除。
  • 将专用数据与窗口相关联的效率低下。
  • 要调用子类链中的下一个过程,不能强制转换旧窗口过程并调用它,而必须 CallWindowProc 函数来进行调用。

使用 ComCtl32.dll 版本 6 对控件子类化

注意

ComCtl32.dll 版本 6 仅支持 Unicode。 ComCtl32.dll 版本 6 支持的常用控件不应与 ANSI 窗口程序进行子类化(或超类化)。

 

ComCtl32.dll 版本 6 包含四个函数,可简化子类创建过程,同时消除前面所讨论的缺点。 新函数封装了与多组引用数据相关的管理,因此开发人员可以专注于编程功能,而不是管理子类。 子类化函数包括:

SetWindowSubclass

该函数用于对窗口进行初始子类化。 每个子类都由 pfnSubclass 及其 uIdSubclass 的地址唯一标识。 这两个函数都是 SetWindowSubclass 函数的参数。 多个子类可以共享同一个子类过程,并且 ID 可以识别每次调用。 要更改引用数据,可以随后调用 SetWindowSubclass。 其重要优点在于,每个子类实例都有自己的引用数据。

子类过程的声明与普通窗口过程略有不同,因为它多了两个数据:子类 ID 和引用数据。 下面函数声明的最后两个参数就说明了这一点。

LRESULT CALLBACK MyWndProc (HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass,
DWORD_PTR dwRefData);

新窗口程序每次收到信息时,都会包含子类 ID 和引用数据。

注意

即使预处理器定义中没有指定 Unicode,传入过程的所有字符串都是 Unicode 字符串。

 

以下示例显示了一个子类化控件的窗口过程的框架实现。

LRESULT CALLBACK OwnerDrawButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                               LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uMsg)
    {
    case WM_PAINT:
        .
        .
        .
        return TRUE;
    // Other cases...
    } 
    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

窗口过程可以附加到对话框过程的 WM_INITDIALOG 处理程序中的控件上,如下例所示。

case WM_INITDIALOG:
{
    HWND button = GetDlgItem(hDlg, IDC_OWNERDRAWBUTTON);
    SetWindowSubclass(button, OwnerDrawButtonProc, 0, 0);
    return TRUE;
}

GetWindowSubclass

此函数可检索子类的相关信息。 例如,可以使用 GetWindowSubclass 来访问引用数据。

RemoveWindowSubclass

此函数用于删除子类。 RemoveWindowSubclass 结合 SetWindowSubclass 可以动态添加和删除子类。

DefSubclassProc

DefSubclassProc 函数会调用子类链中的下一个处理程序。 该函数还会检索适当的 ID 和引用数据,并将信息传递给下一个窗口过程。