自定义控件

本节包含有关应用程序定义或自定义控件的信息。

论述了以下主题。

创建所有者绘制的控件

在创建按钮、菜单、静态文本控件、列表框和组合框时,可以使用所有者绘制的样式标志。 当控件采用所有者绘制样式时,系统会像往常一样处理用户与控件的交互,执行的任务包括检测用户何时选择了按钮,并将事件通知按钮的所有者。 不过,由于控件是所有者绘制的,因此控件的视觉外观由控件的父窗口负责。 每当必须绘制控件时,父窗口就会收到一条消息。

对于按钮和静态文本控件,所有者绘制样式会影响系统绘制整个控件的方式。 对于列表框和组合框,父窗口会绘制控件内的项目,而控件会绘制自己的轮廓。 例如,应用程序可以自定义列表框,以便在列表中每个项目旁边显示一个小位图。

下面的示例代码展示了如何创建所有者绘制的静态文本控件。 假设定义了 Unicode。

// g_myStatic is a global HWND variable.
g_myStatic = CreateWindowEx(0, L"STATIC", L"Some static text", 
            WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, 
            25, 125, 150, 20, hDlg, 0, 0, 0);

在下面的示例中,在包含上一示例中创建的控件的对话框的窗口过程中,通过使用默认字体以自定义颜色显示文本来处理 WM_DRAWITEM 消息。 请注意,在处理 WM_DRAWITEM 时,无需调用 BeginPaintEndPaint

case WM_DRAWITEM:
{
    LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
    if (pDIS->hwndItem == g_myStatic)
    {
        SetTextColor(pDIS->hDC, RGB(100, 0, 100));
        WCHAR staticText[99];
        int len = SendMessage(myStatic, WM_GETTEXT, 
            ARRAYSIZE(staticText), (LPARAM)staticText);
        TextOut(pDIS->hDC, pDIS->rcItem.left, pDIS->rcItem.top, staticText, len);
    }
    return TRUE;
}

有关所有者绘制控件的详细信息,请参阅创建所有者绘制的列表框所有者绘制的组合框

为现有控件的 Window 类创建子类

为现有控件创建子类是创建自定义控件的另一种方法。 创建子类的过程可以通过处理影响选定行为的消息来改变控件的选定行为。 所有其他消息都会传递到控件的原始窗口过程。 例如,应用程序可以为控件创建子类并处理 WM_PAINT 消息,从而在该控件的文本旁显示一个小位图。 有关详细信息,请参阅关于窗口过程为控件创建子类

实现应用程序定义的窗口类

要创建一个不是明确基于现有控件的控件,应用程序必须创建并注册一个窗口类。 为自定义控件注册应用程序定义的窗口类的过程与为普通窗口注册类的过程相同。 要创建自定义控件,请在 CreateWindowEx 函数或对话框模板中指定窗口类的名称。 每个类都必须具有唯一的名称、相应的窗口过程和其他信息。

窗口过程至少要绘制控件。 如果应用程序使用该控件让用户键入信息,窗口过程也会处理来自键盘和鼠标的输入信息,并向父窗口发送通知消息。 此外,如果控件支持控制消息,则窗口过程会处理父窗口或其他窗口发送给它的消息。 例如,控件通常会处理对话框发送的 WM_GETDLGCODE 消息,以指示对话框以特定方式处理键盘输入。

如果下表中的任何预定义控件消息会影响控件的运行,则应用程序定义的控件的窗口过程应处理该消息。

Message 建议
WM_GETDLGCODE 如果控件使用 ENTER、ESC、TAB 或箭头键,则进行处理。 IsDialogMessage 函数会将此消息发送给对话框中的控件,以确定是处理按键还是将按键传递给控件。
WM_GETFONT 如果 WM_SETFONT 消息也会被处理,则进行处理。
WM_GETTEXT 如果控件文本与 CreateWindowEx 函数指定的标题不一致,则进行处理。
WM_GETTEXTLENGTH 如果控件文本与 CreateWindowEx 函数指定的标题不一致,则进行处理。
WM_KILLFOCUS 如果控件显示插入符号、焦点矩形或其他项以指示控件具有输入焦点,则进行处理。
WM_SETFOCUS 如果控件显示插入符号、焦点矩形或其他项以指示控件具有输入焦点,则进行处理。
WM_SETTEXT 如果控件文本与 CreateWindowEx 函数指定的标题不一致,则进行处理。
WM_SETFONT 如果控件会显示文本,则进行处理。 在创建具有 DS_SETFONT 样式的对话框时,系统会发送此消息。

 

应用程序定义的控件消息是特定于给定控件的,必须通过使用 SendMessageSendDlgItemMessage 函数显式发送到控件。 每个消息的数值必须是唯一的,且不得与其他窗口信息的数值相冲突。 为确保应用程序定义的消息值不会发生冲突,在创建每个值时,应用程序应在 WM_USER 值中添加一个唯一编号。

从控件发送通知

自定义控件可能需要向父窗口发送事件通知,以便主机应用程序对这些事件做出响应。 例如,自定义列表视图可能会在用户选择一个项目时发送一个通知,而在双击一个项目时发送另一个通知。

通知以 WM_COMMANDWM_NOTIFY 消息的形式发送。 WM_NOTIFY 消息比 WM_COMMAND 消息包含更多的信息。

控制标识符是应用程序用来识别发送信息的控件的唯一编号。 应用程序会在创建控件时设置控件的标识符。 应用程序可在 CreateWindowEx 函数的 hMenu 参数或 DLGITEMTEMPLATEEX 结构的 id 成员中指定标识符。

由于控件本身并不设置控件标识符,因此控件必须先检索标识符,然后才能发送通知消息。 控件必须使用 GetDlgCtrlID 函数来检索自己的控件标识符。 虽然在创建控件时已将控件标识符指定为菜单句柄,但无法使用 GetMenu 函数来检索标识符。 或者,控件可以在处理 WM_CREATE 消息时,从 CREATESTRUCT 结构中的 hMenu 成员检索标识符。

下面的示例显示了发送特定于控制的通知的两种方式,其中 hwndControl 是控件窗口句柄,而 CN_VALUECHANGED 是自定义通知定义。

 // Send as WM_COMMAND.
SendMessage(GetParent(hwndControl), 
    WM_COMMAND,
    MAKEWPARAM(GetDlgCtrlID(hwndControl), CN_VALUECHANGED),
    (LPARAM)hwndControl);

// Send as WM_NOTIFY.           
NMHDR nmh;
nmh.code = CN_VALUECHANGED;
nmh.idFrom = GetDlgCtrlID(hwndControl);
nmh.hwndFrom = hwndControl;
SendMessage(GetParent(hwndControl), 
    WM_NOTIFY, 
    (WPARAM)hwndControl, 
    (LPARAM)&nmh);

请注意,NMHDR 结构可以是包含附加信息的更大控件定义结构的一部分。 在此示例中,该结构中可能会包含控件的新旧值。 (许多标准通知都使用了此类扩展结构;例如,使用了 NMLISTVIEW 结构的 LVN_INSERTITEM。)

辅助功能

所有常用控件都支持 Microsoft Active Accessibility (MSAA),它可让屏幕阅读器等无障碍技术应用程序进行编程访问。 MSAA 还能让 UI Automation(一种较新的技术)与控件进行交互。

自定义控件应实现 IAccessible 接口(以支持 MSAA)或 UI Automation 接口,或同时实现这两个接口。 否则,无障碍技术产品将只能获得非常有限的控件窗口信息,无法访问控件的属性,也无法触发控件中的事件。

有关让控件可访问的详细信息,请参阅 Windows Automation API

Conceptual

常规控件参考

使用自定义绘图自定义控件的外观

控件消息

对所有者绘制的控件使用视觉样式