TN014:自定义控件
此说明描述 MFC 对自定义和自我绘制控件的支持。 它还描述了动态子类化以及 CWnd 对象与 HWND
之间的关系。
MFC 示例应用程序 CTRLTEST 演示了如何使用许多自定义控件。 有关 MFC 常规示例 CTRLTEST 和联机帮助,请参阅源代码。
所有者描述控件/菜单
Windows 通过使用 Windows 消息为所有者描述控件和菜单提供支持。 任何控件或菜单的父窗口都将接收这些消息并调用函数作为响应。 您可重写这些函数来自定义所有者描述控件或菜单的可视外观和行为。
MFC 通过下列函数直接支持所有者描述:
您可在 CWnd
派生类中重写这些函数来实现自定义描述行为。
此方法不会产生可重用的代码。 如果您在两个不同的 CWnd
类中具有两个相似的控件,则必须在这两个位置实现自定义控件行为。 MFC 支持的自我描述控件体系结构解决了此问题。
自我描述控件和菜单
MFC 为标准所有者描述消息提供了默认实现(位于 CWnd
和 CMenu 类中)。 此默认实现将解码所有者描述参数,并将所有者描述消息委派给控件或菜单。 这称为“自我描述”,因为描述代码位于控件或菜单的类中,而不是所有者窗口中。
通过使用自我描述控件,您可生成使用所有者描述语义显示控件的可重用控件类。 描述控件的代码位于控件类而不是其父级中。 这是面向对象来自定义控件编程的方式。 将下面的函数列表添加到自我描述类:
对于自我描述按钮:
CButton:DrawItem(LPDRAWITEMSTRUCT); // insert code to draw this button
对于自我描述菜单:
CMenu:MeasureItem(LPMEASUREITEMSTRUCT); // insert code to measure the size of an item in this menu CMenu:DrawItem(LPDRAWITEMSTRUCT); // insert code to draw an item in this menu
对于自我描述列表框:
CListBox:MeasureItem(LPMEASUREITEMSTRUCT); // insert code to measure the size of an item in this list box CListBox:DrawItem(LPDRAWITEMSTRUCT); // insert code to draw an item in this list box CListBox:CompareItem(LPCOMPAREITEMSTRUCT); // insert code to compare two items in this list box if LBS_SORT CListBox:DeleteItem(LPDELETEITEMSTRUCT); // insert code to delete an item from this list box
对于自我描述组合框:
CComboBox:MeasureItem(LPMEASUREITEMSTRUCT); // insert code to measure the size of an item in this combo box CComboBox:DrawItem(LPDRAWITEMSTRUCT); // insert code to draw an item in this combo box CComboBox:CompareItem(LPCOMPAREITEMSTRUCT); // insert code to compare two items in this combo box if CBS_SORT CComboBox:DeleteItem(LPDELETEITEMSTRUCT); // insert code to delete an item from this combo box
有关所有者描述结构(DRAWITEMSTRUCT、MEASUREITEMSTRUCT、COMPAREITEMSTRUCT 和 DELETEITEMSTRUCT)的详细信息,请分别参阅 MFC 文档中的 CWnd::OnDrawItem
、CWnd::OnMeasureItem
、CWnd::OnCompareItem
和 CWnd::OnDeleteItem
部分。
使用自我描述控件和菜单
对于自我描述菜单,您必须重写 OnMeasureItem
和 OnDrawItem
方法。
对于自我描述列表框和组合框,您必须重写 OnMeasureItem
和 OnDrawItem
。 必须为对话框模板中的列表框指定 LBS_OWNERDRAWVARIABLE 样式或为组合框指定 CBS_OWNERDRAWVARIABLE 样式。 OWNERDRAWFIXED 样式不适用于自我描述项,因为固定项高度已在自我描述控件附加到列表框之前确定。 (可以使用 CListBox::SetItemHeight 和 CComboBox::SetItemHeight 方法来克服此限制。)
切换到 OWNERDRAWVARIABLE 样式将强制系统对控件应用 NOINTEGRALHEIGHT 样式。 由于控件无法计算大小不定的项的整体高度,因此将忽略 INTEGRALHEIGHT 的默认样式,并且控件将始终为 NOINTEGRALHEIGHT。 如果项目的高度是固定的,则可通过指定控件大小为项目大小的整数倍数来防止描述部分项目。
对于 LBS_SORT 或 CBS_SORT 样式的自我描述列表框和组合框,必须重写 OnCompareItem
方法。
对于自我描述列表框和组合框,一般不会重写 OnDeleteItem
。 如果要执行任何特殊处理,您可重写 OnDeleteItem
。 此操作可能适用的一种情况是,每个列表框或组合框项目与其他内存或其他资源一起存储。
自我描述控件和菜单的示例
MFC 常规示例 CTRLTEST 提供自我描述菜单和自我描述列表框的示例。
自我描述按钮最典型的示例是位图按钮。 位图按钮是显示了不同状态的一个、两个或三个位图图像的按钮。 MFC 类 CBitmapButton 中提供此按钮的示例。
动态子类化
您偶尔需要更改已存在对象的功能。 之前的示例需要您在创建控件之前自定义控件。 利用动态子类化,您可自定义已经创建的控件。
子类化是一个 Windows 术语,用于将窗口的 WndProc 替换为自定义的 WndProc
并为默认功能调用旧的 WndProc
。
这不应与 C++ 类派生混淆。 为清晰说明,C++ 术语“基类”和“派生类”类似于 Windows 对象模型中的“超类”和“子类”。 使用 MFC 的 C++ 派生和 Windows 子类化在功能上是相同的,只不过 C++ 不支持动态子类化。
CWnd
类提供了 C++ 对象(派生自 CWnd
)与 Windows 窗口对象(称为 HWND
)之间的连接。
它们有 3 种常见的相关方式:
CWnd
将创建HWND
。 您可通过创建派生自CWnd
的类来修改派生类的行为。 应用程序调用 CWnd::Create 时将创建HWND
。应用程序会将
CWnd
附加到现有HWND
。 未修改现有窗口的行为。 这是一种委托情况,可以通过调用 CWnd::Attach 将现有HWND
提供为CWnd
对象的别名来实现。CWnd
将附加到现有HWND
,并且您可修改派生类的行为。 这称为动态子类化,因为我们在运行时更改 Windows 对象的行为,并进而更改类。
可以使用 CWnd::SubclassWindow 和CWnd::SubclassDlgItem 方法实现动态子类化。
这两个例程均会将一个 CWnd
对象附加到现有 HWND
。 SubclassWindow
将直接采用 HWND
。 SubclassDlgItem
是采用控件 ID 和父窗口的帮助器函数。 SubclassDlgItem
旨在将 C++ 对象附加到通过对话框模板创建的对话控件。
有关何时使用 SubclassWindow
和 SubclassDlgItem
的多个示例,请参阅 CTRLTEST 示例。