在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,itemRect.top,m_ClientRect.right,itemRect.bottom); itemState = GetItemState(currentItem,TVIF_STATE); if (itemRect.top>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; rcTemp.top += 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,rcTemp.top - 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, rcTemp.top ); 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, rcItem.top + 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; rcTemp.top += 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,rcTemp.top - 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, rcTemp.top ); 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(); }