在CTreeCtrl中加载背景图片,网上有很多例子,有的可行有的不行,这两天一边看资料一边整理,自己写了一个用CxImage加载图片的方法,大家可以参考下。有的地方还没有完善,不过基本功能可以实现,而且添加图片后屏幕不闪烁。已经试过了。
SetReDraw():保证其不要在子节点弹出时重画,而是在子节点已经扩展后重画
在做程序时,遇到了一个很白痴的问题,就是我想要实现鼠标滚动消息时,写了之后调试代码进不去,经过我查看,把ON_WM_MOUSEWHELL放到前面就可以了
(一)使用CxImage可以添加任意的图片
1、在.h中添加静态库
#include "ximage.h"
#pragma comment(lib,"cximage.lib")
#pragma comment(lib,"Jpeg.lib")
#pragma comment(lib,"png.lib")
#pragma comment(lib,"zlib.lib")
2、并且声明两个关于CxImage的变量,用来存放背景图片
CxImage *m_TreeBkImage; //TreeCtrl的背景图片
CxImage *m_Text BkImage; //字的背景图片
3、定义消息防止屏幕闪烁
afx_msg BOOL OnEraseBkgnd(CDC *pDC);
afx_msg void OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnItemexpanded(NMHDR* pNMHDR, LRESULT* pResult);
(二)在.cpp中初始化定义的变量
m_TreeBkImage = new CxImage(); //图片背景
m_TextBkImage = new CxImage(); //字的背景
ON_WM_ERASEBKGND()
ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING,OnItemexpanding)
ON_NOTIFY_REFLECT(TVN_ITEMEXPANDED,OnItemexpanded)
1、在OnPaint()调用整体的图片、字体等
void UITreeCtrl::OnPaint()
{
CPaintDC dc(this); // device context for painting
GetClientRect(&m_ClientRect);
CBitmap bitmap;
CDC MemeDc;
MemeDc.CreateCompatibleDC(&dc);
bitmap.CreateCompatibleBitmap(&dc, m_ClientRect.Width(), m_ClientRect.Height());
CBitmap *pOldBitmap = MemeDc.SelectObject(&bitmap);
DrawBack(&MemeDc);
DrawItem(&MemeDc);
2、画图片背景
void UITreeCtrl::DrawBack(CDC* pDC)
{
if(m_IsDrawBack == TRUE)
{
m_LoadDC.CreateCompatibleDC(NULL);//创建兼容DC
m_LoadDC.SelectObject(&m_bitmap);//将兼容位图选入兼容DC,
m_LoadDC.FillSolidRect(&m_ClientRect,RGB(255,255,255));//首先将bakeBitmap(客户区)填充成背景颜色,这里用的是白色。
PaintImage(pDC,&m_ClientRect,&m_LoadDC);
m_LoadDC.DeleteDC();
}
else
pDC->FillSolidRect(&m_ClientRect,m_TreeBkcolor);
}
dc.BitBlt( m_ClientRect.left, m_ClientRect.top, m_ClientRect.Width(), m_ClientRect.Height(), &MemeDc, 0, 0,SRCCOPY);
MemeDc.SelectObject(pOldBitmap);
MemeDc.DeleteDC();
}
3、重绘每一项
void UITreeCtrl::DrawItem(CDC* pDc)
{
HTREEITEM currentItem,parentItem;//当前的句柄,和它的父节点的句柄
DWORD treeStyle;// 数的类型
CRect itemRect;//每一项的区域
int itemState;//某项的状态
currentItem = GetFirstVisibleItem();//获取第一个课可见的项
do
{
if (GetItemRect(currentItem,itemRect,TRUE))
{
itemRect.left=itemRect.left-15;
CRect fillRect(0,,m_ClientRect.right,itemRect.bottom);
itemState = GetItemState(currentItem,TVIF_STATE);
if (>m_ClientRect.bottom) //说明这一项已超出窗口的边界,所以不绘制
break;
DrawItemText(pDc,currentItem,itemRect);
}
} while ((currentItem=GetNextVisibleItem(currentItem)) != NULL);
}
4、重绘字体以及”+“”-“连线
void UITreeCtrl::DrawItemText(CDC * pDc,HTREEITEM hItem,CRect pRect)
{
int itemState;//某项的状态
DWORD dwStyle = GetStyle();
CRect rcItem,rcTemp,SelRect;//文字位置
CPoint ptTemp;
UINT indent = GetIndent();
SelRect = pRect;
SelRect.left += 15;
pDc->SelectObject(&m_font); //字的大小
itemState = GetItemState(hItem,CDIS_SELECTED);
if(itemState &TVIS_SELECTED) //选中时的背景颜色和字体颜色改变
{
pDc->SetTextColor(m_STextcolor);
DrawTextImage(pDc,&SelRect);
}
else
pDc->SetTextColor(m_textcolor);
CString ItemText = GetItemText(hItem);
CSize fontSize;
fontSize= pDc->GetTextExtent(ItemText);
rcTemp = pRect;
rcTemp.left += 18;
+= 2;
pDc->SetBkMode(TRANSPARENT);
pDc->DrawText(ItemText,rcTemp,DT_LEFT|DT_TOP);//显示项文本
GetItemRect(hItem,&rcItem,TRUE); //取得Item的文本矩形范围
//2、如果要画线
if(dwStyle & TVS_HASLINES)
{
//创建一个真正的点线画笔
LOGBRUSH logBrush;
logBrush.lbColor = m_textcolor;
logBrush.lbStyle = BS_SOLID;
CPen pen(PS_COSMETIC | PS_ALTERNATE, 1, &logBrush);
CPen* oldPen = pDc->SelectObject(&pen);
rcTemp = rcItem;
rcTemp.left -= indent;//从当前向左移动一个 缩进 的量
rcTemp.right = rcTemp.left + indent;
ptTemp = rcTemp.CenterPoint();
//如果 Item 有父 Item 则在自己面前画一个'L'型的线,拐点正是缩进矩形的中心点
if( GetParentItem(hItem) != NULL )
{
//如果 Item 有一个弟弟节点(哥哥排上面), 则在自己面前画一条竖线, 否则画半条
pDc->MoveTo( ptTemp.x - 1, - 1 );
if(GetNextSiblingItem(hItem) != NULL)
pDc->LineTo( ptTemp.x - 1, rcTemp.bottom );
else
pDc->LineTo( ptTemp.x - 1, ptTemp.y - 1 );
pDc->MoveTo( rcTemp.right, ptTemp.y - 1 );
pDc->LineTo( ptTemp.x - 1, ptTemp.y - 1 );
}
//依次绘制各个 Item 的父节点与叔叔节点之间被撑开的部分的连线
HTREEITEM hItemTemp = hItem;
while( hItemTemp = GetParentItem(hItemTemp) )
{
rcTemp.OffsetRect(-indent, 0);
ptTemp = rcTemp.CenterPoint();
if(GetNextSiblingItem(hItemTemp))
{
pDc->MoveTo( ptTemp.x - 1, );
pDc->LineTo( ptTemp.x - 1, rcTemp.bottom );
}
}
//显示删除 MFC GDI 对象, 因为以前版本的 MFC 貌似有个BUG, 如果你不显示删除它就不会帮你删除, 这个BUG好像还存在
pDc->SelectObject(oldPen);
pen.DeleteObject();
pen.DeleteTempMap();
}
//3、绘制小'+'框
if(dwStyle & TVS_HASBUTTONS)
{
rcItem.left -= indent;
rcItem.right = rcItem.left + indent;
//确定绘制范围
rcTemp.SetRect(0, 0, 9, 9);
rcTemp.OffsetRect(rcItem.CenterPoint());
rcTemp.OffsetRect(-5, -5);
//绘制, 因为使用的是MFC10.0, 因此实际绘制工作可交给 CMFCVisualManager 完成,其实... 自己画难度也不大
if( ItemHasChildren(hItem) )
CMFCVisualManager::GetInstance()->OnDrawExpandingBox(pDc, rcTemp, ( GetItemState(hItem, TVIS_EXPANDED) & TVIS_EXPANDED ?TRUE:FALSE), RGB(0, 0, 0));
}
}
5、从本地加载图片需要知道图片的路径,定义函数专门加载图片的路径
CString UITreeCtrl::ReturnPicLoad()
{
//定义路径
CString path; //获取系统参数
GetModuleFileName(NULL,path.GetBufferSetLength(MAX_PATH+1),MAX_PATH);
path.ReleaseBuffer();
int pos = path.ReverseFind('\\');
m_exePath = path.Left(pos+1);
return m_exePath;
}
6、知道了图片的路径才能加载图片,如果路径不正确,则没有图片出现
void UITreeCtrl::AddLoadPicture(CString pSrc)
{
if(pPic == pSrc) return;
else m_IsDrawBack = TRUE;
pPic = pSrc;
m_TreeBkImage->Load(m_exePath+pPic,FindType(pPic));
m_TreeBkImage->Draw(pDC->GetSafeHdc(),pRect->left,pRect->top,pRect->Width(),pRect->Height(),0,true);
pDC->BitBlt(pRect->left,pRect->top,pRect->Width(),pRect->Height(),pSrc,0,0,SRCCOPY);
}
7、在程序运行过程中会出现闪烁问题,虽然网上有很多解决方式大同小异,但是还是想写下,这样比较代码比较完善
void UITreeCtrl::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
SetRedraw(FALSE);
*pResult = 0;
}
void UITreeCtrl::OnItemexpanded(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
Invalidate();
SetRedraw(TRUE);
*pResult = 0;
}
8、很多朋友看到上面那个FindType这个函数没有定义 ,这个主要是根据加载的图片可以找到图片的后缀类型
int UITreeCtrl::FindType(CString filename)
{
//根据图片找到图片的后缀类型
CString ext = filename.Right(filename.GetLength()-filename.ReverseFind('.')-1);
int type = 0;
if (ext == _T("bmp")) type = CXIMAGE_FORMAT_BMP;
#if CXIMAGE_SUPPORT_JPG
else if (ext == _T("jpg") || ext == _T("jpeg")) type = CXIMAGE_FORMAT_JPG;
#endif
#if CXIMAGE_SUPPORT_GIF
else if (ext == _T("gif")) type = CXIMAGE_FORMAT_GIF;
#endif
#if CXIMAGE_SUPPORT_PNG
else if (ext == _T("png")) type = CXIMAGE_FORMAT_PNG;
#endif
else type = CXIMAGE_FORMAT_MNG;
<span style="white-space: pre;"> </span>return type;<span style="white-space: pre;"> </span>
}
BOOL UITreeCtrl::OnEraseBkgnd(CDC *pDC)
{
return true;
}
最核心的内容就是上面的了
下面我再介绍一种方式,可以重绘TreeCtrl的这两种方式我都试过的。可行。
void UITreeCtrl::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
LPNMTVCUSTOMDRAW pCustomDraw = (LPNMTVCUSTOMDRAW) pNMCD;
NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
HTREEITEM hItem = (HTREEITEM) pLVCD->nmcd.dwItemSpec;
*pResult = CDRF_DODEFAULT;
CDC *pDC = CDC::FromHandle(pLVCD->nmcd.hdc);
switch (pLVCD-> nmcd.dwDrawStage)
{
case CDDS_PREPAINT: //绘制控件前
{
*pResult = CDRF_NOTIFYITEMDRAW;
break;
}
case CDDS_ITEMPREPAINT: // 一个项被绘制前
{
CRect rcItem(pNMCD->rc);
rcClient = rcItem;
CPoint ptItem(rcItem.left + 1, + 1);
if (!hItem)
return;
//画文字、线、"+"号
DrawItem(hItem,pCustomDraw);
*pResult = CDRF_SKIPDEFAULT;//告诉系统跳过默认处理
return;
}
break;
}
}
void UITreeCtrl::DrawItem(HTREEITEM hItem,LPNMTVCUSTOMDRAW lpNMTVCD)
{
DWORD dwStyle = GetStyle();
CRect rcItem,rcTemp;
CPoint ptTemp;
UINT indent = GetIndent();
//取得 Item 文本的矩形范围
GetItemRect(hItem, &rcItem, TRUE);
CDC dc;
dc.Attach(lpNMTVCD->nmcd.hdc);
if(lpNMTVCD->nmcd.uItemState & CDIS_SELECTED)
{ //如果 Item 被选中
dc.SetTextColor(RGB(0,0,255));
dc.FillSolidRect(&rcItem,m_STextcolor);
}
else
{
dc.SetTextColor(m_textcolor);
}
dc.SetBkMode(TRANSPARENT);
rcTemp = rcItem;
rcTemp.left += 2;
+= 2;
//1、绘制文本
dc.DrawText(GetItemText(hItem), &rcTemp, DT_LEFT | DT_SINGLELINE | DT_VCENTER);
//2、如果要画线
if(dwStyle & TVS_HASLINES)
{
//创建一个真正的点线画笔
LOGBRUSH logBrush;
logBrush.lbColor = m_textcolor;
logBrush.lbStyle = BS_SOLID;
CPen pen(PS_COSMETIC | PS_ALTERNATE, 1, &logBrush);
CPen* oldPen = dc.SelectObject(&pen);
rcTemp = rcItem;
rcTemp.left -= indent;//从当前向左移动一个 缩进 的量
rcTemp.right = rcTemp.left + indent;
ptTemp = rcTemp.CenterPoint();
//如果 Item 有父 Item 则在自己面前画一个'L'型的线,拐点正是缩进矩形的中心点
if( GetParentItem(hItem) != NULL )
{
//如果 Item 有一个弟弟节点(哥哥排上面), 则在自己面前画一条竖线, 否则画半条
dc.MoveTo( ptTemp.x - 1, - 1 );
if(GetNextSiblingItem(hItem) != NULL)
dc.LineTo( ptTemp.x - 1, rcTemp.bottom );
else dc.LineTo( ptTemp.x - 1, ptTemp.y - 1 );
dc.MoveTo( rcTemp.right, ptTemp.y - 1 );
dc.LineTo( ptTemp.x - 1, ptTemp.y - 1 );
}
//依次绘制各个 Item 的父节点与叔叔节点之间被撑开的部分的连线
HTREEITEM hItemTemp = hItem;
while( hItemTemp = GetParentItem(hItemTemp) )
{
rcTemp.OffsetRect(-indent, 0);
ptTemp = rcTemp.CenterPoint();
if(GetNextSiblingItem(hItemTemp))
{
dc.MoveTo( ptTemp.x - 1, );
dc.LineTo( ptTemp.x - 1, rcTemp.bottom );
}
}
//显示删除 MFC GDI 对象, 因为以前版本的 MFC 貌似有个BUG, 如果你不显示删除它就不会帮你删除, 这个BUG好像还存在
dc.SelectObject(oldPen);
pen.DeleteObject();
pen.DeleteTempMap();
}
//3、绘制小'+'框
if(dwStyle & TVS_HASBUTTONS)
{
rcItem.left -= indent;
rcItem.right = rcItem.left + indent;
//确定绘制范围
rcTemp.SetRect(0, 0, 9, 9);
rcTemp.OffsetRect(rcItem.CenterPoint());
rcTemp.OffsetRect(-5, -5);
//绘制, 因为使用的是MFC10.0, 因此实际绘制工作可交给 CMFCVisualManager 完成,其实... 自己画难度也不大
if( ItemHasChildren(hItem) )
CMFCVisualManager::GetInstance()->OnDrawExpandingBox(&dc, rcTemp, ( GetItemState(hItem, TVIS_EXPANDED) & TVIS_EXPANDED ?TRUE:FALSE), RGB(0, 0, 0));
}
//完成
dc.Detach();
}