common control 4.7版本介绍了一个新的特性叫做Custom Draw,这个名字显得模糊不清,让人有点摸不着头脑,而且MSDN里也只给出了一些如风的解释和例子,没有谁告诉你你想知道的,和究竟这个特性有什么好处。

Custom draw可以被想象成一个轻量级的,容易使用的重绘方法(重绘方法还有几种,例如Owner Draw等)。这种容易来自于我们只需要处理一个消息(NM_CUSTOMDRAW),就可以让Windows为你干活了,你就不用被逼去处理"重绘过程"中所有的脏活了。

这篇文章的焦点是如何在一个LISTCTRL控件上使用Custom Draw消息。究其原因,一部分是因为我已经在我的工作上使用了Custom Draw有一段时间了,我很熟悉它。另一个原因是这个机制确实是非常好用,你只需要写很少量的代码就可以达到很好的效果。使用 Custom draw 来对控件外观编程甚至可以代替很多的古老方法。

以下代码是在WIN98 和VC6 SP2的环境下写的,common controls DLL的版本是5.0。我已经对其在WinNT 4上进行了测试。系统要运行这些代码,它的common controls DLL的版本必须至少是4.71。但随着IE4 的发布,这已经不是问题了。(IE会夹带着这个DLL一起发布)

Custom Draw 基础

我将会尽我所能把Custom Draw的处理描述清楚,而不是简单的引用MSDN的文档。这些例子都需要你的程序有一个ListCtrl在对话框上,并且这个ListCtrl处于Report和多列模式。

Custom Draw 的消息映射入口

Custom draw 是一个类似于回调的处理过程,Windows在绘制List Ctrl的某个时间点上通过 Notification 消息通知你的程序,你可以选择忽略所有的通知(这样你就会看到标准的ListCtrl),或者处理某部分的绘制(实现简单的效果),甚至整个的控件都由你来绘制(就象使用Owner-Drawing一样)。这个机制的真正卖点是:你只需要实现一些你需要的,其余的可以让Windows为你代劳。

好了,现在你可以开始为你的ListCtrl添加Custom Draw去做一些个性化的事情了。你首先要有正确的Comm Ctrl Dll版本,然后Windows会为你发送NM_CUSTOMDRAW消息,你只需要添加一个处理函数以便开始使用Custom draw。首先添加一个消息映射,象下面一样:

ON_NOTIFY ( NM_CUSTOMDRAW, IDC_MY_LIST, OnCustomdrawMyList )

处理函数的原形如下:

afx_msg void OnCustomdrawMyList ( NMHDR* pNMHDR, LRESULT* pResult );

这就告诉MFC你要处理从你的ListCtrl控件发出的WM_NOTIFY消息,ID为IDC_MY_LIST,通知码为NM_CUSTOMDRAWOnCustomdrawMyList就是你的处理函数。

如果你有一个从ClistCtr派生的类,你想为它添加custom draw,你就可以使用ON_NOTIFY_REFLECT来代替。如下:

ON_NOTIFY_REFLECT ( NM_CUSTOMDRAW, OnCustomdraw )

OnCustomdraw的原形和上面的函数一致,但它是声明在你的派生类里的。

Custom draw将控件的绘制分为两部分:擦除和绘画。Windows在每部分的开始和结束都会发送NM_CUSTOMDRAW消息。所以总共就有4个消息。但是实际上你的程序所收到消息可能就只有1个或者多于四个,这取决于你想要让WINDOWS怎么做。每次发送消息的时段被称作为一个“绘画段”。你必须紧紧抓住这个概念,因为它贯穿于整个“重绘”的过程。

所以,你将会在以下的时间点收到通知:

l         一个item被画之前——“绘画前”段

l         一个item被画之后——“绘画后”段

l         一个item被擦除之前——“擦除前”段

l         一个item被擦除之后——“擦除后”段

并不是所有的消息都是一样有用的,实际上,我不需要处理所有的消息,直到这篇文章完成之前,我还没使用过擦除前和擦除后的消息。所以,不要被这些消息吓到你。

NM_CUSTOMDRAW Messages提供给你的信息:

l         NM_CUSTOMDRAW消息将会给你提供以下的信息:

l         ListCtrl的句柄

l         ListCtrl的ID

l         当前的“绘画段”

l         绘画的DC,让你可以用它来画画

l         正在被绘制的控件、item、subitem的RECT值

l         正在被绘制的Item的Index值

l         正在被绘制的SubItem的Index值

l         正被绘制的Item的状态值(selected, grayed, 等等)

l         Item的LPARAM值,就是你使用CListCtrl::SetItemData所设的那个值

上述所有的信息对你来说可能都很重要,这取决于你想实现什么效果,但最经常用到的就是“绘画段”、“绘画DC”、“Item Index”、“LPARAM”这几个值。

一个简单的例子:

好了,经过上面的无聊的细节之后,我们是时候来看一些简单的代码了。第一个例子非常的简单,它只是改变了一下控件中文字的颜色。

处理的代码如下:

 
 
void CPanel1::OnCustomdrawList ( NMHDR* pNMHDR, LRESULT* pResult )
{NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
    // Take the default processing unless we set this to something else below.*pResult = 0;
    // First thing - check the draw stage. If it's the control's prepaint// stage, then tell Windows we want messages for every item.if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage ){*pResult = CDRF_NOTIFYITEMDRAW;}else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage ){// This is the prepaint stage for an item. Here's where we set the// item's text color. Our return value will tell Windows to draw the// item itself, but it will use the new color we set here.// We'll cycle the colors through red, green, and light blue.COLORREF crText;
        if ( (pLVCD->nmcd.dwItemSpec % 3) == 0 )crText = RGB(255,0,0);else if ( (pLVCD->nmcd.dwItemSpec % 3) == 1 )crText = RGB(0,255,0);elsecrText = RGB(128,128,255);
        // Store the color back in the NMLVCUSTOMDRAW struct.pLVCD->clrText = crText;
        // Tell Windows to paint the control itself.*pResult = CDRF_DODEFAULT;}
}
 

结果如下,你可以看到行和行间的颜色的交错显示,多酷,而这只需要两个if的判断就可以做到了。

有一件事情必须记住,在做任何的绘画之前,你都要检查正处身的“绘画段”,因为你的处理函数会接收到非常多的消息,而“绘画段”将决定你代码的行为。

一个更小的简单例子:

下面的例子将演示怎么去处理subitem的绘画(其实subitem也就是列)

  1. 在ListCtrl控件绘画前处理NM_CUSTOMDRAW消息。
  2. 告诉Windows我们想对每个Item处理NM_CUSTOMDRAW消息。
  3. 当这些消息中的一个到来,告诉Windows我们想在每个SubItem的绘制前处理这个消息
  4. 当这些消息到达,我们就为每个SubItem设置文字和背景的颜色。

void CMyDlg::OnCustomdrawMyList ( NMHDR* pNMHDR, LRESULT* pResult )

{

NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );

// Take the default processing unless we set this to something else below.

*pResult = CDRF_DODEFAULT;

// First thing - check the draw stage. If it's the control's prepaint

// stage, then tell Windows we want messages for every item.

if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )

{

*pResult = CDRF_NOTIFYITEMDRAW;

}

elseif ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )

{

// This is the notification message for an item. We'll request

// notifications before each subitem's prepaint stage.

*pResult = CDRF_NOTIFYSUBITEMDRAW;

}

elseif ( (CDDS_ITEMPREPAINT | CDDS_SUBITEM) == pLVCD->nmcd.dwDrawStage )

{

// This is the prepaint stage for a subitem. Here's where we set the

// item's text and background colors. Our return value will tell

// Windows to draw the subitem itself, but it will use the new colors

// we set here.

// The text color will cycle through red, green, and light blue.

// The background color will be light blue for column 0, red for

// column 1, and black for column 2.

COLORREF crText, crBkgnd;

if ( 0 == pLVCD->iSubItem )

{

crText = RGB(255,0,0);

crBkgnd = RGB(128,128,255);

}

elseif ( 1 == pLVCD->iSubItem )

{

crText = RGB(0,255,0);

crBkgnd = RGB(255,0,0);

}

else

{

crText = RGB(128,128,255);

crBkgnd = RGB(0,0,0);

}

// Store the colors back in the NMLVCUSTOMDRAW struct.

pLVCD->clrText = crText;

pLVCD->clrTextBk = crBkgnd;

// Tell Windows to paint the control itself.

*pResult = CDRF_DODEFAULT;

}

}

执行的结果如下:

这里需要注意两件事:

l         clrTextBk的颜色只是针对每一列,在最后一列的右边那个区域颜色也还是和ListCtrl控件的背景颜色一致。

l         当我重新看文档的时候,我注意到有一篇题目是“NM_CUSTOMDRAW (list view)”的文章,它说你可以在最开始的custom draw消息中返回CDRF_NOTIFYSUBITEMDRAW就可以处理SubItem了,而不需要在CDDS_ITEMPREPAINT绘画段中去指定CDRF_NOTIFYSUBITEMDRAW。但是我试了一下,发现这种方法并不起作用,你还是需要处理CDDS_ITEMPREPAINT段。

处理“绘画之后”的段

到限制为止的例子都是处理“绘画前”的段,当Windows绘制List Item之前就改变它的外观。然而,在“绘制前”,你的绘制行为时被限制的,你只能改变字体的颜色或者外观。如果你想改变图标的绘制,你可以在“绘画前”把整个 Item重画或者在“绘画后”去做这件事。当你做在绘画后去做“自定义绘画”是,你的“绘画处理函数”就会在Windows画完整个Item或者SubItem的时候被调用,你就可以随心所欲的乱画了!!

在这个例子里,我将创建一个ListCtrl,一般的ListCtrl的Item如果被选择了,则其Icon也会呈现出被选择的状态。而我创建的这个ListCtrl的Icon是不会呈现被选择的状态的。步骤如下:

  1. 对ListCtrl在“绘画前”处理NM_CUSTOMDRAW消息。
  2. 告诉Windows我们想在每个Item被画的时候获得NM_CUSTOMDRAW消息。
  3. 当这些消息来临,告诉Windows我们想在你画完的时候获取NM_CUSTOMDRAW消息。
  4. 当这些消息来到的时候,我们就重新画每一个Item的图标。
 
void CPanel3::OnCustomdrawList ( NMHDR* pNMHDR, LRESULT* pResult )
{NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );*pResult = 0;// If this is the beginning of the control's paint cycle, request// notifications for each item.if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage ){*pResult = CDRF_NOTIFYITEMDRAW;}else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage ){// This is the pre-paint stage for an item.  We need to make another// request to be notified during the post-paint stage.*pResult = CDRF_NOTIFYPOSTPAINT;}else if ( CDDS_ITEMPOSTPAINT == pLVCD->nmcd.dwDrawStage ){// If this item is selected, re-draw the icon in its normal// color (not blended with the highlight color).LVITEM rItem;int    nItem = static_cast<int>( pLVCD->nmcd.dwItemSpec );// Get the image index and state of this item.  Note that we need to// check the selected state manually.  The docs _say_ that the// item's state is in pLVCD->nmcd.uItemState, but during my testing// it was always equal to 0x0201, which doesn't make sense, since// the max CDIS_ constant in commctrl.h is 0x0100.ZeroMemory ( &rItem, sizeof(LVITEM) );rItem.mask  = LVIF_IMAGE | LVIF_STATE;rItem.iItem = nItem;rItem.stateMask = LVIS_SELECTED;m_list.GetItem ( &rItem );// If this item is selected, redraw the icon with its normal colors.if ( rItem.state & LVIS_SELECTED ){CDC*  pDC = CDC::FromHandle ( pLVCD->nmcd.hdc );CRect rcIcon;// Get the rect that holds the item's icon.m_list.GetItemRect ( nItem, &rcIcon, LVIR_ICON );// Draw the icon.m_imglist.Draw ( pDC, rItem.iImage, rcIcon.TopLeft(),ILD_TRANSPARENT );*pResult = CDRF_SKIPDEFAULT;}}
}
 

重复,custom draw让我们可以做尽可能少的工作,上面的例子就是让Windows帮我们做完全部的工作,然后我们就重新对选择状态的Item的图标做重画,那就是我们看到的那个图标。执行结果如下:

唯一的不足是,这样的方法会让你感觉到一点闪烁。因为图标被画了两次(虽然很快)。

用Custom Draw代替Owner Draw

另外一件优雅的事情就是你可以使用Custom Draw来代替Owner Draw。它们之间的不同在我看来就是:

l         写Custom Draw的代码比写Owner Draw的代码更容易。

如果你只需要改变某行的外观,你可以不用管其他的行的绘画,让WINDOWS去做就行了。但如果你使用

Owner Draw,你必须要对所有的行作处理。当你想对控件作所有的处理时,你可以在处理NM_CUSTOMDRAW

消息的最后返回CDRF_SKIPDEFAULT,这有点和我们到目前为止所做的有些不同。CDRF_SKIPDEFAULT

告诉Windows由我们来做所有的控件绘画,你不用管任何事。

我没有在这里包含这个例子的代码,因为它有点长,但是你可以一步步地在调试器中调试代码,你可以看到每一

步发生了什么。如果你把窗口摆放好,让你可以看到调试器和演示的程序,那在你一步步的调试中,你可以看到

控件每一步的绘制,这里的ListCtrl是很简单的,只有一列并且没有列头,如下:

如果需要看原文和下载例子程序,请到这个网址:

http://www.codeproject.com/listctrl/lvcustomdraw.asp

NM_CUSTOMDRAW消息相关推荐

  1. VC控件自绘制三步曲

    实现自定义绘制的三步曲 既然您已经了解了绘制控件可用的各种选项(包括使用自定义绘制的好处),那么,让我们来看看实现一个自定义绘制控件需要的三个主要步骤. 执行一个 NM_CUSTOMDRAW 消息处理 ...

  2. 【MFC】定义XP风格的工具栏

    00. 目录 文章目录 00. 目录 01. 案例概述 02. 开发环境 03. 关键技术 04. 程序设计 05. 秘笈心法 06. 源码下载 07. 附录 01. 案例概述 网上的许多软件都具有漂 ...

  3. MFC窗口风格 WS_style/WS_EX_style

    窗口风格(Window style) WS_BORDER   有边框窗口 WS_CAPTION   必须和WS_BORDER风格配合,但不能与WS_DLGFRAME风格一起使用.指示窗口包含标题要部分 ...

  4. mfc,WM_CTLCOLOR,WM_PAINT

    1. WM_ERASEBKGND 绘制背景 WM_PAINT 绘制前景,就是一般的绘图 WM_CTLCOLOR 确切的说对于edit 其实是 WM_CTLCOLOREDIT ,反射消息,表示子控件绘制 ...

  5. 20200220MFC 之CListCtrl(二)

    本文根据本人在项目中的应用,来谈谈CListCtrl的部分用法及技巧.当初学习时,查了很多资料,零零碎碎的作了些记录,现在主要是来做个总结,方便以后查阅.主要包括以下十三点内容:基本操作.获取选中行的 ...

  6. [WTL/ATL]_[中级]_[自定义TrackBar]

    场景 开发WTL/ATL界面程序时, 有时候会需要微调控件进行缓慢增减数值. 但是微调控件在大数值调整上往往比较麻烦,需要点击很多次.这时就需要使用TrackBar控件[1]进行大数值的快速变化. 标 ...

  7. CListCtrl 灰白相间实现

    CListCtrl 灰白相间实现: 需要处理NM_CUSTOMDRAW消息,特定绘制list 1 初始化 消息映射: 3 函数实现: 效果图:

  8. VC++ 繁星音乐播放器

    繁星音乐播放器是本人第二次使用VC++编写的小应用.主要实现音乐的导入,删除和现实歌词,另外有一些特效 一.功能需求: 1.实现基本的音乐播放器的播放.暂停.继续.停止.上一首.下一首. 2.实现音乐 ...

  9. Custom Draw

    common control 4.7版本介绍了一个新的特性叫做Custom Draw,这个名字显得模糊不清,让人有点摸不着头脑,而且MSDN里也只给出了一些如风的解释和例子,没有谁告诉你你想知道的,和 ...

最新文章

  1. ACL 2021 最佳论文:最优运输理论开启词表学习新纪元
  2. 8_用opencv调用深度学习框架tenorflow、Pytorch、Torch、caffe训练好的模型(20190212)
  3. TCP/IP——基本知识
  4. 《说服力——让你的PPT会说话》读书笔记02
  5. 惠普服务器显示灯闪红灯,惠普打印机指示灯闪烁什么意思? 惠普2130打印机故障灯大全图解...
  6. failed to launch: nice -n 0 $HADOOP_HOME/bin/spark-class org.apache.spark.deploy.worker.Worker
  7. 30行Python代码来绘制一个微信图标
  8. AI 换脸?除了 ZAO,用 Python 一样轻松搞定
  9. ST众泰:未与长城汽车签署过任何战略协议
  10. [转载] python 中 setattr() 函数 和 getarrr() 函数
  11. 使用pyenv和virtualenv搭建python虚拟环境
  12. python 图片对比文件夹_Python挑选文件夹里宽大于300图片的方法
  13. 2020华为软件精英挑战赛热身赛总结篇
  14. 求n的阶乘【VB代码实现】
  15. PAT 乙级 Java 合集
  16. (8)seetaface6 JNI之人脸相似度1:1(包含戴口罩人脸相似度)计算
  17. Windows最强ssh客户端推荐 —— Bitvise SSH Client(一)
  18. mysql 全文索引无效_为什么MySQL全文索引不起作用?
  19. NetWare网络操作系统
  20. Office文件转PDF的解决方案

热门文章

  1. ImageBox网页图片批量下载器
  2. 分享免费的主流电商平台商品图片批量下载方法
  3. 深入了解Elasticsearch存储
  4. mybatis/mybatis plus lambda会话缓存失效(1)
  5. Java实现家谱家族管理系统,图形化家谱家族树,单机应用程序
  6. python爬虫实现批量下载百度图片
  7. 2-eggs-100-floors-puzzle(扔两个鸡蛋问题)
  8. entity、bo、vo、po、dto、pojo如何理解及相互转化
  9. 柱状图怎么设置xy轴_excel表格xy轴数据-怎么做用excel设置xy轴
  10. 11 Animation动画