Custom draw vs Owner draw

本文深入探讨了在不同情况下使用自定义绘制(CustomDraw)的功能,包括如何通过该特性定制控件外观,以及与自定义绘制(OwnerDraw)的区别。文章覆盖了通用控件的自定义,如滑块控件,以及如何通过自定义绘制实现更灵活的外观定制。

From: http://www.codeproject.com/KB/miscctrl/CustomDrawCSliderCtrl.aspx

http://www.codeproject.com/Articles/8985/Customizing-the-Appearance-of-CSliderCtrl-Using-Cu


"Custom Draw" is a feature shared by all of Microsoft's common controls, which allows you to interject your own code while the control is drawing itself. In this way, you can customize the appearance of the control, altering it to suit your needs and tastes.

There have been a number of great articles on custom draw, in the context of various different controls. One of the best is Michael Dunn's "Neat Stuff to do in List Controls Using Custom Draw", which illustrates how to tame an unruly custom draw interface to customize the appearance of aCListCtrl (which is a common listview control). Other examples and a good explanation can be found at MSDN, beginning with the article "Customizing a Control's Appearance Using Custom Draw".

"Custom Draw" is not the same thing as "Owner Draw". Custom draw notifications are sent out automatically (it's up to you to catch the notifications and process them correctly), whereas for "owner draw", you need to set a style flag (such as LVS_OWNERDRAWFIXED for a list-view control) before the control will send you a WM_DRAWITEM message. In custom draw, the control pretty much draws itself completely, and your code customizes its appearance only here-and-there, whereas for owner draw, you must draw everything, usually in an OnDrawItem handler, even if you want to make the most trivial change. Custom draw is available only for the common controls (like Header controls, List-view controls, Rebar controls, Toolbar controls, ToolTip controls, Trackbar controls, and Tree-view controls) but is not available for the standard (and older) Windows controls (like button controls, combo box controls, list box controls, progress bar controls, static controls, and tab controls); owner draw, on the other hand, is available for all controls.

You can think of custom draw as a kind of "light-weight" owner draw (Michael Dunn's words), where the control does most of the work, and you modify just a bit of it. In the hierarchy of things, if you like the way a control looks and how it functions, then use the control "as is"; if you like most of the way the control looks and how it functions, then use custom draw; if you don't like the way a control looks but you like how it functions, then use Owner draw; and if you don't like the way it looks or the way it functions, then write your own custom control.


<think>我们正在处理关于MFC中HDN_CUSTOMDRAW消息的问题。用户可能在使用MFC的列表控件(如CListCtrl)时,想要自定义绘制其头部(HeaderControl),而HDN_CUSTOMDRAW消息正是用于此目的。但是用户遇到了找不到该消息的问题。首先,我们需要明确:HDN_CUSTOMDRAW是HeaderControl发送的通知消息,用于自定义绘制。在MFC中,我们通常通过消息映射来处理这类消息。根据引用[3]中提到的,HDN_CUSTOMDRAW对应的结构体应该是NMCUSTOMDRAW(但实际上,对于HeaderControl,它使用的是NMCUSTOMDRAW的一个扩展结构,即NMCUSTOMDRAW本身,或者更具体地说,是NMHDR和NMCUSTOMDRAW的组合)。然而,用户提到“找不到该消息”,可能有以下原因:1.消息映射中没有正确添加该消息。2.使用的类不是HeaderControl的父窗口(通常是CListCtrl的父窗口)或者没有在正确的窗口中处理。3.可能拼写错误或者消息宏使用不正确。在MFC中,处理HDN_CUSTOMDRAW消息的步骤:1.在父窗口类(例如,包含CListCtrl的对话框或视图类)中声明消息处理函数。2.在消息映射中添加ON_NOTIFY宏,指定消息码HDN_CUSTOMDRAW。3.实现消息处理函数。但是,HDN_CUSTOMDRAW消息是通知父窗口的,所以我们应该在父窗口类中处理。示例代码:在头文件中声明处理函数:```cpp//假设我们的对话框类为CMyDialogclassCMyDialog:publicCDialogEx{//...其他代码protected:afx_msgvoidOnHdnCustomdrawList(NMHDR*pNMHDR,LRESULT*pResult);DECLARE_MESSAGE_MAP()};```在cpp文件中添加消息映射:```cppBEGIN_MESSAGE_MAP(CMyDialog,CDialogEx)ON_NOTIFY(HDN_CUSTOMDRAW,0,OnHdnCustomdrawList)//这里的0是列表控件头部的ID,如果头部是子控件,需要指定正确的IDEND_MESSAGE_MAP()```注意:第二个参数是子控件的ID,如果我们的列表控件头部是列表控件的一部分(通常情况),那么列表控件的头部没有单独的ID。因此,我们需要使用列表控件的ID,并将消息映射改为处理列表控件的通知。但是,HDN_CUSTOMDRAW是头部控件发送给父窗口的,所以我们可以这样写:实际上,对于CListCtrl的头部自定义绘制,我们通常使用列表控件的ID,然后在ON_NOTIFY宏中指定消息为HDN_CUSTOMDRAW,并且第三个参数是处理函数。但是,由于头部控件是列表控件的子控件,所以通知消息是通过列表控件转发给父窗口的,因此我们需要在ON_NOTIFY宏中指定列表控件的ID。例如,如果列表控件的ID是IDC_LIST1,那么:```cppBEGIN_MESSAGE_MAP(CMyDialog,CDialogEx)ON_NOTIFY(HDN_CUSTOMDRAW,IDC_LIST1,OnHdnCustomdrawList)END_MESSAGE_MAP()```但是,这里有一个问题:HDN_CUSTOMDRAW消息的发送者是头部控件,而列表控件只是转发。在MFC的消息映射中,ON_NOTIFY的第一个参数是通知码,第二个参数是发送通知的控件的ID。但是,头部控件并不是我们直接创建的,它属于列表控件,因此它的ID实际上是列表控件的ID(因为头部控件没有单独的ID,所以通知消息中,NMHDR的hwndFrom是头部控件的句柄,idFrom是列表控件的ID)。因此,我们在ON_NOTIFY中使用的控件ID应该是列表控件的ID。然后,在实现处理函数时:```cppvoidCMyDialog::OnHdnCustomdrawList(NMHDR*pNMHDR,LRESULT*pResult){//我们需要将pNMHDR转换为指向NMCUSTOMDRAW结构体的指针LPNMCUSTOMDRAWpNMCD=reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);//或者,因为头部控件使用NMCUSTOMDRAW,但实际上对于HeaderControl,它使用的是LPNMHEADER(在自定义绘制时,实际上还是NMCUSTOMDRAW)?//注意:HDN_CUSTOMDRAW对应的结构体实际上是NMCUSTOMDRAW,但我们可以通过pNMHDR->code来区分阶段。//根据绘制阶段进行处理if(pNMCD->dwDrawStage==CDDS_PREPAINT){//返回CDRF_NOTIFYITEMDRAW,以便我们接收每个项目的绘制通知*pResult=CDRF_NOTIFYITEMDRAW;return;}elseif(pNMCD->dwDrawStage==CDDS_ITEMPREPAINT){//这是头部的一个项目要绘制//我们可以在这里设置文本颜色、背景等//通过pNMCD->dwItemSpec来获取当前项目的索引//例如,设置文本颜色为红色CDC*pDC=CDC::FromHandle(pNMCD->hdc);pDC->SetTextColor(RGB(255,0,0));//我们也可以设置背景颜色//注意:如果需要自定义背景,可能需要处理CDDS_ITEMPREERASE等阶段*pResult=CDRF_DODEFAULT;//让系统绘制默认的,但我们修改了文本颜色//或者返回CDRF_NEWFONT表示我们修改了字体等,需要重新计算,但这里我们只修改了颜色//实际上,修改颜色可以直接返回CDRF_DODEFAULT,因为系统会使用我们设置的颜色。return;}*pResult=0;}```注意:以上代码是示例,实际使用时需要根据需求调整。如果用户在使用时遇到“找不到HDN_CUSTOMDRAW”的问题,可能是以下原因:1.没有包含必要的头文件:需要包含<afxcmn.h>(因为列表控件是公共控件)。2.消息映射中写错了通知码:应该是HDN_CUSTOMDRAW,但注意HDN_CUSTOMDRAW是一个宏,定义在commctrl.h中,如果包含头文件顺序问题,可能导致未定义。确保包含了<commctrl.h>或<afxcmn.h>(后者会包含前者)。3.控件的ID不正确:在ON_NOTIFY宏中,第二个参数是发送通知的控件的ID,这里应该是列表控件的ID(因为头部控件的通知是通过列表控件转发的,其idFrom为列表控件的ID)。另外,如果用户使用的是较新版本的VisualStudio,可能需要检查SDK版本和头文件是否包含正确。如果用户仍然找不到HDN_CUSTOMDRAW,可以尝试使用自定义消息映射,或者使用ON_NOTIFY_REFLECT来处理(如果是在派生自CListCtrl的类中处理,则使用反射消息)。但是,根据引用[3]中提到的,HDN_CUSTOMDRAW对应的结构体是NMCUSTOMDRAW,所以我们可以按照上述方法处理。最后,建议用户参考MSDN文档:https://docs.microsoft.com/en-us/windows/win32/controls/hdn-customdraw由于用户提到“找不到”,可能是没有正确包含头文件,或者消息映射没有正确设置。因此,我们总结步骤:1.确保包含必要的头文件(如afxcmn.h)。2.在父窗口类中声明消息处理函数。3.在消息映射中使用ON_NOTIFY(HDN_CUSTOMDRAW,列表控件ID,处理函数)。4.实现处理函数,处理不同的绘制阶段。如果问题仍然存在,请检查列表控件是否创建成功,以及是否使用了正确的ID。现在,我们为用户提供一个完整的示例,假设在对话框中使用CListCtrl,并自定义其头部绘制。注意:在实际编程中,可能需要根据需求调整绘制代码。相关问题:1.如何在MFC中处理其他自定义绘制消息(如列表控件本身的自定义绘制)?2.除了自定义绘制,还有哪些方法可以改变控件的外观?3.在自定义绘制时如何避免闪烁?请根据用户的实际需求选择相关问题。</think>### MFC中HDN_CUSTOMDRAW消息的使用方法 在MFC中使用`HDN_CUSTOMDRAW`消息进行Header Control(表头控件)的自定义绘制时,需要以下步骤: #### 1. **添加消息处理函数** 在父窗口类(如对话框类)头文件中声明消息处理函数: ```cpp // MyDialog.h class CMyDialog : public CDialogEx { DECLARE_MESSAGE_MAP() afx_msg void OnHdnCustomdraw(NMHDR* pNMHDR, LRESULT* pResult); }; ``` #### 2. **定义消息映射** 在cpp文件中添加消息映射宏: ```cpp // MyDialog.cpp BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx) ON_NOTIFY(HDN_CUSTOMDRAW, 0, &CMyDialog::OnHdnCustomdraw) END_MESSAGE_MAP() ``` **注意**: 第二个参数`0`表示处理所有Header Control的通知(若需指定控件ID,替换为控件ID值)[^2]。 #### 3. **实现消息处理函数** ```cpp void CMyDialog::OnHdnCustomdraw(NMHDR* pNMHDR, LRESULT* pResult) { // 转换为NMCUSTOMDRAW结构体 LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR); switch (pNMCD->dwDrawStage) { case CDDS_PREPAINT: // 绘制开始前 *pResult = CDRF_NOTIFYITEMDRAW; // 请求逐项绘制通知 return; case CDDS_ITEMPREPAINT: // 绘制表头项前 if (pNMCD->dwItemSpec == 0) // 第一列 { // 设置红色文本 ::SetTextColor(pNMCD->hdc, RGB(255, 0, 0)); } *pResult = CDRF_DODEFAULT; // 继续默认绘制 return; } *pResult = 0; } ``` #### 4. **常见问题解决** **Q: 编译时报"HDN_CUSTOMDRAW未定义"** 原因:缺少Windows头文件。 解决方法: 在`stdafx.h`中添加: ```cpp #include <commctrl.h> // 包含Header Control定义 #define _WIN32_IE 0x0500 // 确保支持自定义绘制 ``` **Q: 消息处理函数未被调用** 检查点: 1. 确认控件已启用`Owner Draw`属性(在资源编辑器中设置) 2. 确认消息映射中的控件ID与资源ID一致 3. 调试检查`pNMHDR->hwndFrom`是否为Header Control句柄 #### 5. **关键结构体说明** ```mermaid graph TD A[NMHDR] -->|基础结构| B(hwndFrom) A --> C(idFrom) A --> D(code) E[NMCUSTOMDRAW] -->|扩展结构| F(dwDrawStage) E --> G(hdc) E --> H(rc) E --> I(dwItemSpec) // 当前项索引 ``` [^3] --- ### 相关问题 1. **如何在MFC中实现Header Control的完全自绘?** 2. **如何动态修改Header Control的列宽时触发重绘?** 3. **`NMCUSTOMDRAW`与`NMLVCUSTOMDRAW`在绘制列表控件时有何区别?** 4. **如何处理Header Control的点击事件与绘制协同工作?** --- > 提示:自定义绘制时需注意绘制阶段处理,`CDDS_PREPAINT`返回`CDRF_NOTIFYITEMDRAW`才能接收逐项通知。若需高级效果(如渐变背景),可在`CDDS_ITEMPREPAINT`阶段直接操作DC绘制[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值