让人迷惑的Custom Draw与Owner draw

本文探讨了Windows编程中CustomDraw与OwnerDraw的区别。CustomDraw允许开发者改变控件的某些属性并让系统完成绘制,而OwnerDraw则需要开发者自行处理所有绘制任务。文章还介绍了如何通过处理NM_CUSTOMDRAW消息来实现CustomDraw。
      最近忙于美化界面,然而wince上面提供的功能比桌面上要少的多,网上可供选择的控件也少的可怜(后来发现还是有些的,没有调研就没有发言权啊!:()。好在功夫不负有心人,借鉴别人的代码,也给我倒腾出带图片的按钮、不规则形状按钮、可在每行中显示图片的listctrl。希望最近有时间可以把这些整理出来写成博客,这次先不提这。这里要说的让人迷惑的Custom Draw与Owner draw。
      可能有不少人对于这两个概念不怎么了解,或者没有听说过,更不用说它们有什么区别了!不幸的是,我之前也是其中一个...
      从字面上来看,custom draw应该可以解释成定制式的绘制,而owner draw可以表达为自定义绘制。那么先看看何谓定制?通俗的说,就是
  1. 改变控件的某些属性(控件的背景色),然后让windows来处理绘制的工作。
  2. 只想自己绘制控件的某些部分,让windows来处理其他的部分。
  3. 完全自己来绘制整个控件,那么告诉windows什么都不用做。
说到这,owner draw,可以等同于上面说的第三种情况,就是你必须处理绘制控件的所有工作。所要做的工作更多,更复杂,所花费的时间也更多。因而custom draw是首选,没办法了,再选owner draw--喜欢没事找事?...
      那么custom draw如何绘制和通知windows你的决定呢?这就必须要处理NM_CUSTOMDRAW消息。接收该消息的函数原型如下:
                   void OnCustomdrawMyList ( NMHDR* pNMHDR, LRESULT* pResult )
其中参数pNMHDR包含有控件的窗口句柄,id等,当然重要的是包含了当前所处的绘制阶段信息,总共有4个阶段:
  • 画之前
  • 画之后
  • 擦除之前
  • 擦除之后
你可以在相应的阶段进行绘制工作,并通知windows要处理哪些工作,那么如何通知呢?通过参数pResult。

可能你觉得奇怪,这就结束了?本来我是打算根据看的博客原文详细翻译成中文的,可惜被抢先了。那么不如只概要的总体上描述,毕竟不管原文还是翻译的文章都比较长。有兴趣深究的可以看下面的链接:
原文: http://www.codeproject.com/KB/list/lvcustomdraw.aspx?msg=875581
翻译: http://blog.csdn.net/dylgsy/article/details/818550
<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、付费专栏及课程。

余额充值