自绘树控件的关键之处在于NM_CUSTOMDRAW消息,比如tree节点的颜色,list某行的字体的颜色
可以看到在CTree 的NM_CUSTOMDRAW消息处理函数里有这样一句:
void CNewTreeCtrl::On
{
NMTVCUSTOMDRAW *ptvTreeCtrl=(NMTVCUSTOMDRAW *)pNMHDR;
//一个细小失误
}
void CNewTreeCtrl::OnCustomdrawFilelist(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVCUSTOMDRAW* pLVCD = (NMLVCUSTOMDRAW*)(pNMHDR);
}
{
此函数由类向导产生 注意去掉*pResult = 0; 否则无效果
}
这个是类型转换,为什么要类型转换呢,因为能收到NM_CUSTOMDRAW不止树控件一个,其它的控件也能收到,这样里面的结构就不尽相同,所以不同的控件,对于函数的第一个参数pNMHDR有不同的转换。这里给一张从MSDN复制过来的的表,各控件对应的转化结构。
结构对应一览表:(表格没复制过来,看着不差就行了,将就一下)
Control | Structure |
---|---|
List view | NMLVCUSTOMDRAW |
ToolTips | NMTTCUSTOMDRAW |
Tree view | NMTVCUSTOMDRAW |
All other supported controls | NMCUSTOMDRAW |
------------------------------------------------------------------------------------------------------
可见树控件对应的是NMTVCUSTOMDRAW结构,这里我们就来了解一下这个结构
NMTVCUSTOMDRAW结构定义:
typedef struct tagNMTVCUSTOMDRAW {
NMCUSTOMDRAW nmcd;//一个包含控件各项信息的结构,如设备上下方句柄,项状态,大小。(参见后面)
COLORREF clrText;//如果项有文本,这个指明文本颜色
COLORREF clrTextBk;//文本背景色
}NMTVCUSTOMDRAW, *LPNMTVCUSTOMDRAW;
--------------------------------------------------------------------------------------------------------------------
NMCUSTOMDRAW结构定义:
typedef struct tagNMCUSTOMDRAWINFO {
NMHDR hdr;//这个结构变量跟On
//(DWORD)&pTreeCtrl->nmcd.hdr和(DWORD)pNMHDR的值是相等的,转来转去又转回来了。
DWORD dwDrawStage;//绘画段,这个要着重了解,后面介绍,篇幅较大
HDC hdc;//控件的设备上下文句柄
RECT rc;//要绘制的区域,大小,一般是一个项的区域,如果是大循环则是整个控件的大小。
DWORD dwItemSpec;//项索引,依据控件而来(如CListCtrl),树控件不需要这个变量
UINT uItemState;//项的状态,详见后面
LPARAM lItemlParam //项关联的数据,通过SetItemData函数设置的。
}NMCUSTOMDRAW, FAR* LPNMCUSTOMDRAW;
----------------------------------------------------------------------------------------------------------------------
先来看看关于dwDrawState在MSDN的取值:
The following table shows the global Drawstage values.
Value Description
CDDS_POSTERASE After the erasing cycle is complete. 擦除控件后
CDDS_POSTPAINT After the painting cycle is complete. 绘制控件后
CDDS_PREERASE Before the erasing cycle begins. 擦除控件前
CDDS_PREPAINT Before the painting cycle begins. 绘制控件前
The following table shows the global Drawstage values.
Value Description
CDDS_ITEM Indicates that the dwItemSpec, uItemState, and lItemParam members are valid.
CDDS_ITEMPOSTERASE After an item has been erased. 一个项被擦除后
CDDS_ITEMPOSTPAINT After an item has been drawn. 一个项被绘制好后
CDDS_ITEMPREERASE Before an item is erased. 一个项被擦除前
CDDS_ITEMPREPAINT Before an item is drawn. 一个项被绘制前
NMCUSTOMDRAW消息自绘,使你可以选择在哪个阶段来自绘,比如我在绘制前这个阶段绘制了这个控件,默认的话,是看不到结果的,因为会被控件重新绘制,覆盖掉的。那么我就在绘制后这个阶段来绘制,那么这样就可以看到结果了,但这样,会有两次绘制,徒增了一次绘制,降低了效率,而且还会产生闪烁。
解决的方法是在一项被绘制前,指定*pResult为CDRF_SKIPDEFAULT,跳过默认绘制。这样就可以在绘制前这一个阶段来绘制了,不会被覆盖。
再来说说擦除和绘制的顺序,一次绘制控件里的循环顺序,是以CDDS_PREPAINT开始,CDDS_POSTPAINT结束的。那么在这个过程中,控件就会给父窗口发送很多消息,通知父窗口,现在是一个项绘制前这个阶段,现在又到了一项被擦除后这个阶段了。可是有时候我们并不需要处理所有的阶段,比如我只要在一个项被绘制前处理一下,那要怎么办呢,当然,你也可以直接用IF语句,判断dwDrawStage就行了,
如果是CDDS_ITEMPREPAINT就处理,这也可以,但我所要做的是,指定控件只有在绘制一个项前的时候发送通知消息给父窗口,其余的就不需要了,这个也是由On
擦除和绘制的顺序很好理解,谁先谁后,试想,比如我要绘制一个控件。那么这个状态就是绘制前,然后再擦除。
所以顺序是CDDS_PREPAINT->CDDS_PREERASE->CDDS_POSTERASE->CDDS_POSTPAINT
这是一个绘制控件的大循环。
前面说过了,pResult指定控件在哪个阶段发送通知过来,而CDDS_PRPAINT是自绘的开端,所以就有这样的规定,当dwDrawStage为
CDDS_PRPAINT的时候,必须指定pResult的值,以告诉控件我要处理哪些阶段。(PS:上面那个大循环,我只能接收到CDDS_PREPAINT这个阶段,剩下的三个我根本就接不到,我不知道是什么原因,难道是在CDDS_PREPAINT时候,必须指定pResult的值,但没有一个对应着后面的三个阶段,搞不清楚,不过也没关系,这完全不影响自绘一个控件,我也不想去找了)
当dwDrawStage为CDDS_PRPAINT时pResult必须指定下面的一个值:(从msdn复制过来的)
CDRF_DODEFAULT 告诉控件我什么阶段也不处理,大循环结束了。没了意义。
The control will draw itself. It will not send any additional NM_CUSTOMDRAW messages for this paint cycle.
CDRF_NOTIFYITEMDRAW 处理关于一个项所有的阶段,擦除前,擦除后,绘制前,绘制后。(就是小循环,一个项的绘制,但像大循环那样,我只能接到一个项绘制前的消息。)
The control will notify the parent of any item-related drawing operations. It will send NM_CUSTOMDRAW messages before and after drawing items.
CDRF_NOTIFYITEMERASE 只处理一个项擦除前
The control will notify the parent when an item will be erased. It will send NM_CUSTOMDRAW messages before and after erasing items.
CDRF_NOTIFYPOSTERASE 只处理一个项擦除后
The control will notify the parent after erasing an item.
CDRF_NOTIFYPOSTPAINT 只处理一个项绘制后
The control will notify the parent after painting an item.
(另:擦除也算是绘制的一部分,我估计擦除就是调用类似FillRect的函数用白色画刷填充)
又有当dwDrawStage为 CDDS_ITEMPREPAINT(一个项绘制前)的时候,pReslt必须指定下面的一个值:
CDRF_NEWFONT 项使用新字体,也就是使ptvTreeCtrl->clrText,还有背景色有效。。
Your application specified a new font for the item; the control will use the new font.
CDRF_SKIPDEFAULT 跳过默认项绘制,自己来绘制。
Your application drew the item manually. The control will not draw the item.
uItemState项的状态(来自MSDN)
Specifies the current item state. It can be a combination of the following values.
Value Description
CDIS_CHECKED The item is checked. 项被核记了(如果有核计功能)
CDIS_DEFAULT The item is in its default state. 默认状态(平常的状态)
CDIS_DISABLED The item is disabled. 项被禁止了(不可用)
CDIS_FOCUS The item is in focus. 项具有焦点
CDIS_GRAYED The item is grayed. 项为灰颜色?
CDIS_HOT The item is currently under the pointer (hot). 鼠标当前停留在这个项上(热点)
CDIS_SELECTED The item is selected. 项被选中了(单击)
另外一些 CTreeCtrl类里需要了解的函数:
HTREEITEM HitTest( CPoint pt, UINT* pFlags=NULL );//根据鼠标位置获取其所在的项句柄,后面那个参数,它指明具体在一个项的哪里,图标上,还是文本上。这个参数值默认为NULL,我们也不需要获取。
CString GetItemText( HTREEITEM hItem ) const; //这个根据树项句柄获取项文本内容。
BOOL ItemHasChildren( HTREEITEM hItem );//判断一个项是否有子项,返回假没有子项。
UINT GetItemState( HTREEITEM hItem, UINT nStateMask ) const;//获取一个项的状态。
GetItemState函数第二个参数指明获取哪些状态,为NULL也可以,我这里是要用这个函数来判断一个父项是否为展开状态
也就是:if(GetItemState(hTreeItem,TVIS_EXPANDED)&TVIS_EXPANDED)//为真的话,这个父项处于展开状态
BOOL Expand( HTREEETEM hItem, UINT nCode );//展开或收缩一个项,关于第二个参数nCode有以下解释:
TVE_COLLAPSE 收缩列表
TVE_EXPAND 展开列表
TVE_TOGGLE 如果列表是展开状态,则收缩,反之则展开。
BOOL SelectItem( HTREEITEM hItem );//选中一个项
参考 :http://zhanshaoji.blog.163.com/blog/static/174816245201241710435781/