CTreeCtrl自绘

2023-09-13 08:21:49 浏览数 (2)

代码语言:javascript复制
#pragma once
 
#define _OWNER_DRAWN_TREE  // 自绘CTreeCtrl,可支持背景图片显示功能
 
class CSWTreeCtrl : public CTreeCtrl
{
	DECLARE_DYNAMIC(CSWTreeCtrl)
 
	// 成员私有结构定义
 
	// 构造/析构函数
public:
	CSWTreeCtrl();
	virtual ~CSWTreeCtrl();
 
	// 私有成员变量
private:
 
	// 私有成员函数
private:
	void CommonConstruct(); // 初始化
 
	// 受保护成员变量
protected:
	int m_nFirstColumnWidth; // 第一列宽度
	int m_nOffsetX;      	 // 相对于父窗体X偏移量
	LVBKIMAGE m_bkImage;	 // 背景图片
	CImageList m_imgBtns;	 // 数控件展开的 /-图标符号
 
	// 受保护成员函数
protected:
	BOOL CheckHit(CPoint point);
#ifdef _OWNER_DRAWN_TREE
	LRESULT CustomDrawNotify(LPNMTVCUSTOMDRAW lpNMTVCustomDraw);
	void OwnerDrawBackground(CDC* pDC);
#endif //_OWNER_DRAWN_TREE
 
	// 虚函数
protected:
 
	// 消息函数
protected:
	DECLARE_MESSAGE_MAP()
	afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
	afx_msg void OnPaint();
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
#ifdef _OWNER_DRAWN_TREE
	afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
	afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar);
#endif //_OWNER_DRAWN_TREE
 
	// 共有成员变量
public:
 
	// 共有成员函数
public:
	// 获取背景图片
	BOOL GetBkImage(LVBKIMAGE* plvbkImage) const;
 
	// 设置背景图片
	BOOL SetBkImage(LVBKIMAGE* plvbkImage);
 
#ifdef _OWNER_DRAWN_TREE
	// 设置树控件 /-按钮图标
	void SetTreeBtnBitmap(CBitmap* pBitmap, COLORREF clrMask = RGB(255, 0, 255));
#endif //_OWNER_DRAWN_TREE
 
	// 静态成员变量
public:
 
	// 静态成员函数
public:
 
};

实现

代码语言:javascript复制
#include "stdafx.h"
#include <shlwapi.h>
#include "CSWTreeCtrl.h"
 
// IE 5.0 or higher required
#ifndef TVS_NOHSCROLL
#error CSWTreeCtrl requires IE 5.0 or higher; _WIN32_IE should be greater than 0x500.
#endif
 
 
// 绘画树控件横向节点连接虚线
static void _DotHLine(HDC hdc, LONG x, LONG y, LONG w, COLORREF cr)
{
	for (; w > 0; w -= 2, x  = 2)
		SetPixel(hdc, x, y, cr);
}
 
// 绘画树控件竖向节点连接虚线
static void _DotVLine(HDC hdc, LONG x, LONG y, LONG w, COLORREF cr)
{
	for (; w > 0; w -= 2, y  = 2)
		SetPixel(hdc, x, y, cr);
}
 
IMPLEMENT_DYNAMIC(CSWTreeCtrl, CTreeCtrl)
CSWTreeCtrl::CSWTreeCtrl()
{
	CommonConstruct();
}
 
CSWTreeCtrl::~CSWTreeCtrl()
{
}
 
BEGIN_MESSAGE_MAP(CSWTreeCtrl, CTreeCtrl)
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	ON_WM_VSCROLL()
	ON_WM_MOUSEWHEEL()
	ON_WM_KEYDOWN()
END_MESSAGE_MAP()
 
void CSWTreeCtrl::CommonConstruct()
{
#ifdef _OWNER_DRAWN_TREE 
	m_bkImage.hbm = NULL;
	m_bkImage.xOffsetPercent = 0;
	m_bkImage.yOffsetPercent = 0;
#endif //_OWNER_DRAWN_TREE
}
 
#ifdef _OWNER_DRAWN_TREE
// 设置树控件 /-按钮图标
void CSWTreeCtrl::SetTreeBtnBitmap(CBitmap* pBitmap, COLORREF clrMask)
{
	ASSERT(pBitmap);
	if (pBitmap && pBitmap->GetSafeHandle())
	{
		m_imgBtns.Add(pBitmap, clrMask);
		BITMAP bmp;
		pBitmap->GetBitmap(&bmp);
		if (m_imgBtns.GetSafeHandle())
			m_imgBtns.DeleteImageList();
		m_imgBtns.Create(bmp.bmWidth / 2, bmp.bmHeight, ILC_COLOR32 | ILC_MASK, 2, 1);
	}
}
#endif //_OWNER_DRAWN_TREE
 
BOOL CSWTreeCtrl::GetBkImage(LVBKIMAGE* plvbkImage) const
{
	memcpy(plvbkImage, &m_bkImage, sizeof(LVBKIMAGE));
	return TRUE;
}
 
BOOL CSWTreeCtrl::SetBkImage(LVBKIMAGE* plvbkImage)
{
	memcpy(&m_bkImage, plvbkImage, sizeof(LVBKIMAGE));
	Invalidate();
	return TRUE;
}
 
void CSWTreeCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	Invalidate();
 
	CTreeCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}
 
void CSWTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	if (CheckHit(point))
	{
		HTREEITEM hItem = HitTest(point);
		if (hItem)
		{
#ifdef _OWNER_DRAWN_TREE
			CRect rcItem, rcClient;
			GetClientRect(&rcClient);
			GetItemRect(hItem, &rcItem, FALSE);
			if (rcItem.bottom > rcClient.bottom)
			{
				Invalidate();
				EnsureVisible(hItem);
				SelectItem(hItem);
				return;
			}
#endif //_OWNER_DRAWN_TREE
 
			SelectItem(hItem);
		}
 
		CTreeCtrl::OnLButtonDown(nFlags, point);
	}
	else
	{
		SetFocus();
	}
}
 
void CSWTreeCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	if (CheckHit(point))
	{
		HTREEITEM hItem = HitTest(point);
		if (hItem)
		{
#ifdef _OWNER_DRAWN_TREE
			CRect rcItem, rcClient;
			GetClientRect(&rcClient);
			GetItemRect(hItem, &rcItem, FALSE);
			if (rcItem.bottom > rcClient.bottom)
			{
				Invalidate();
				CTreeCtrl::OnLButtonDown(nFlags, point);
				return;
			}
#endif //_OWNER_DRAWN_TREE
 
			SelectItem(hItem);
		}
 
		CTreeCtrl::OnLButtonDblClk(nFlags, point);
	}
	else
	{
		SetFocus();
	}
}
 
void CSWTreeCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
	if (CheckHit(point))
	{
		CTreeCtrl::OnMouseMove(nFlags, point);
	}
}
 
#ifdef _OWNER_DRAWN_TREE
LRESULT CSWTreeCtrl::CustomDrawNotify(LPNMTVCUSTOMDRAW lpNMTVCustomDraw)
{
	lpNMTVCustomDraw->nmcd.hdr.hwndFrom = GetSafeHwnd();
	lpNMTVCustomDraw->nmcd.hdr.code = NM_CUSTOMDRAW;
	lpNMTVCustomDraw->nmcd.hdr.idFrom = GetWindowLong(m_hWnd, GWL_ID);
	return GetParent()->SendMessage(WM_NOTIFY, (WPARAM)0, (LPARAM)lpNMTVCustomDraw);
}
 
void CSWTreeCtrl::OwnerDrawBackground(CDC* pDC)
{
	NMTVCUSTOMDRAW stuNMTVCustomDraw;
	DWORD dwFlags;
	DWORD dwRet;
	CRect rcClient;
	GetClientRect(&rcClient);
	memset(&stuNMTVCustomDraw, 0, sizeof(NMTVCUSTOMDRAW));
	stuNMTVCustomDraw.nmcd.dwDrawStage = CDDS_PREPAINT;
	stuNMTVCustomDraw.nmcd.hdc = pDC->m_hDC;
	stuNMTVCustomDraw.nmcd.rc = rcClient;
	dwFlags = (DWORD)CustomDrawNotify(&stuNMTVCustomDraw); // CDDS_PREPAINT
 
	// 填充背景色
	COLORREF crBkgnd = IsWindowEnabled() ? pDC->GetBkColor() : GetSysColor(COLOR_BTNFACE);
	pDC->FillSolidRect(rcClient, crBkgnd);
	if (m_bkImage.hbm && IsWindowEnabled())
	{
		// 画背景图片
		int xOffset = m_nOffsetX;
		int yOffset = rcClient.left;
		int yHeight = rcClient.Height();
		SCROLLINFO srlInfo;
		if (GetScrollInfo(SB_VERT, &srlInfo, SIF_POS | SIF_RANGE))
		{
			yOffset = -srlInfo.nPos;
			yHeight = max(srlInfo.nMax   1, rcClient.Height());
		}
 
		CDC dcMem;
		dcMem.CreateCompatibleDC(pDC);
		BITMAP bmp;
		::GetObject(m_bkImage.hbm, sizeof(BITMAP), &bmp);
		CBitmap* pOldBitmap =
			dcMem.SelectObject(CBitmap::FromHandle(m_bkImage.hbm));
 
		// copy the background bitmap from temporary DC to painting DC
		float x = (float)m_bkImage.xOffsetPercent / 100 * (float)rcClient.Width();
		float y = (float)m_bkImage.yOffsetPercent / 100 * (float)rcClient.Height();
		pDC->BitBlt(/*xOffset*/ (int)x,
			/*yOffset */(int)y,
			bmp.bmWidth, bmp.bmHeight, &dcMem, 0, 0, SRCCOPY);
		dcMem.SelectObject(pOldBitmap);
	}
 
	if (dwFlags&CDRF_NOTIFYPOSTERASE)
	{
		stuNMTVCustomDraw.nmcd.dwDrawStage = CDDS_POSTERASE;
		dwRet = (DWORD)CustomDrawNotify(&stuNMTVCustomDraw); // CDDS_POSTERASE
	}
 
	CFont* pOldFont = pDC->SelectObject(GetFont());
	CImageList* pstateList = GetImageList(TVSIL_STATE);
	CImageList* pimgList = GetImageList(TVSIL_NORMAL);
	CSize szIcon, szStateImg;
	if (pimgList)
	{
		IMAGEINFO ii;
		if (pimgList->GetImageInfo(0, &ii))
			szIcon = CSize(ii.rcImage.right - ii.rcImage.left, ii.rcImage.bottom - ii.rcImage.top);
	}
 
	if (pstateList)
	{
		IMAGEINFO ii;
		if (pstateList->GetImageInfo(0, &ii))
			szStateImg = CSize(ii.rcImage.right - ii.rcImage.left, ii.rcImage.bottom - ii.rcImage.top);
	}
 
	// 开始画树节点
	HTREEITEM hItem = GetFirstVisibleItem();
	while (hItem)
	{
		int nOldBkMode = pDC->SetBkMode(TRANSPARENT);
		DWORD dwStyle = GetStyle();
		DWORD dwState = GetItemState(hItem, 0xFFFF);
		BOOL bEnabled = IsWindowEnabled();
		BOOL bSelected = dwState & TVIS_SELECTED;
		BOOL bHasFocus = (GetFocus() && GetFocus()->m_hWnd == m_hWnd) ? TRUE : FALSE;
 
		// 更新NMCUSTOMDRAW结果内容
		stuNMTVCustomDraw.nmcd.dwItemSpec = (DWORD_PTR)hItem;
		if (bEnabled)
		{
			if (bHasFocus)
			{
				stuNMTVCustomDraw.clrTextBk = bSelected ? GetSysColor(COLOR_HIGHLIGHT) : crBkgnd;
				stuNMTVCustomDraw.clrText = ::GetSysColor(bSelected ? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT);
				stuNMTVCustomDraw.nmcd.uItemState = dwState | (bSelected ? CDIS_FOCUS : 0);
			}
			else
			{
				if (GetStyle()&TVS_SHOWSELALWAYS)
				{
					stuNMTVCustomDraw.clrTextBk = bSelected ? GetSysColor(COLOR_INACTIVEBORDER) : crBkgnd;
					stuNMTVCustomDraw.clrText = ::GetSysColor(COLOR_MENUTEXT);
				}
				else
				{
					stuNMTVCustomDraw.clrTextBk = crBkgnd;
					stuNMTVCustomDraw.clrText = ::GetSysColor(COLOR_MENUTEXT);
				}
			}
		}
		else
		{
			stuNMTVCustomDraw.clrTextBk = bSelected ? GetSysColor(COLOR_BTNSHADOW) : crBkgnd;
			stuNMTVCustomDraw.clrText = ::GetSysColor(COLOR_GRAYTEXT);
		}
 
		GetItemRect(hItem, &stuNMTVCustomDraw.nmcd.rc, 0);
		CRgn rgn;
		rgn.CreateRectRgn(stuNMTVCustomDraw.nmcd.rc.left, stuNMTVCustomDraw.nmcd.rc.top, stuNMTVCustomDraw.nmcd.rc.left   m_nFirstColumnWidth, stuNMTVCustomDraw.nmcd.rc.bottom);
		pDC->SelectClipRgn(&rgn);
		dwRet = CDRF_DODEFAULT;
		if (dwFlags&CDRF_NOTIFYITEMDRAW)
		{
			stuNMTVCustomDraw.nmcd.dwDrawStage = CDDS_ITEMPREPAINT;
			dwRet = (DWORD)CustomDrawNotify(&stuNMTVCustomDraw); // CDDS_ITEMPREPAINT
		}
 
		if (!(dwFlags&CDRF_SKIPDEFAULT))
		{
			// 画树节点图标和 /-按钮
			CRect rcItem;
			int nImage, nSelImage;
			GetItemRect(hItem, &rcItem, TRUE);
			GetItemImage(hItem, nImage, nSelImage);
			int x = rcItem.left - 3;
			int yCenterItem = rcItem.top   (rcItem.bottom - rcItem.top) / 2;
			if (pimgList)
			{
				int nImageIndex = dwState & TVIS_SELECTED ? nImage : nSelImage;
				x -= szIcon.cx   1;
				pimgList->Draw(pDC, nImageIndex, CPoint(x, yCenterItem - szIcon.cy / 2), ILD_TRANSPARENT);
			}
 
			if (GetStyle()&TVS_CHECKBOXES && pstateList != NULL)
			{
				DWORD dwStateImg = GetItemState(hItem, TVIS_STATEIMAGEMASK) >> 12;
				x -= szStateImg.cx;
				pstateList->Draw(pDC, dwStateImg, CPoint(x, yCenterItem - szStateImg.cy / 2), ILD_TRANSPARENT);
			}
 
			if (dwStyle&TVS_HASLINES)
			{
				CPen pen;
				pen.CreatePen(PS_DOT, 1, GetLineColor());
				CPen* pOldPen = pDC->SelectObject(&pen);
				HTREEITEM hItemParent = GetParentItem(hItem);
				if (hItemParent != NULL || dwStyle & TVS_LINESATROOT)
				{
					_DotHLine(pDC->m_hDC, x - szIcon.cx / 2 - 2, yCenterItem, szIcon.cx / 2   2, RGB(128, 128, 128));
				}
 
				if (hItemParent != NULL || dwStyle & TVS_LINESATROOT && GetPrevSiblingItem(hItem) != NULL)
				{
					_DotVLine(pDC->m_hDC, x - szIcon.cx / 2 - 2, rcItem.top,
						yCenterItem - rcItem.top, RGB(128, 128, 128));
				}
 
				if (GetNextSiblingItem(hItem) != NULL)
				{
					_DotVLine(pDC->m_hDC, x - szIcon.cx / 2 - 2, yCenterItem,
						rcItem.bottom - yCenterItem, RGB(128, 128, 128));
				}
 
				int x1 = x - szIcon.cx / 2 - 2;
				while (hItemParent != NULL)
				{
					x1 -= szIcon.cx   3;
					if (GetNextSiblingItem(hItemParent) != NULL)
					{
						_DotVLine(pDC->m_hDC, x1, rcItem.top, rcItem.Height(), RGB(128, 128, 128));
					}
					hItemParent = GetParentItem(hItemParent);
				}
 
				pDC->SelectObject(pOldPen);
 
			}
 
			if (dwStyle&TVS_HASBUTTONS && ItemHasChildren(hItem))
			{
				// 画 /-符号按钮
				int nImg = GetItemState(hItem, TVIF_STATE) & TVIS_EXPANDED ? 1 : 0;
				m_imgBtns.Draw(pDC, nImg, CPoint(x - szIcon.cx / 2 - 6, yCenterItem - 4),	ILD_TRANSPARENT);
			}
		}
 
		pDC->SelectClipRgn(NULL);
		if (dwRet&CDRF_NOTIFYPOSTPAINT)
		{
			stuNMTVCustomDraw.nmcd.dwDrawStage = CDDS_ITEMPOSTPAINT;
			dwRet = (DWORD)CustomDrawNotify(&stuNMTVCustomDraw); // CDDS_ITEMPOSTPAINT
		}
 
		pDC->SetBkMode(nOldBkMode);
		hItem = GetNextVisibleItem(hItem);
	};
 
	pDC->SelectObject(pOldFont);
}
 
BOOL CSWTreeCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	Invalidate();
	return CTreeCtrl::OnMouseWheel(nFlags, zDelta, pt);
}
 
void CSWTreeCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
{
	Invalidate();
	CTreeCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}
#endif //_OWNER_DRAWN_TREE
 
void CSWTreeCtrl::OnPaint()
{
	CRect rcClient;
	GetClientRect(&rcClient);
	CPaintDC dc(this);
	CDC dcMem;
	CBitmap bmpMem;
	dcMem.CreateCompatibleDC(&dc);
	if (bmpMem.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height()))
	{
		CBitmap* pOldBmp = dcMem.SelectObject(&bmpMem);
#ifdef _OWNER_DRAWN_TREE	// if owner-drawn
		OwnerDrawBackground(&dcMem);	// use our code
#else						// else use standard code
		CWnd::DefWindowProc(WM_PAINT, (WPARAM)dcMem.m_hDC, 0);
#endif //_OWNER_DRAWN_TREE
		dc.BitBlt(0, 0, rcClient.right, rcClient.bottom, &dcMem, 0, 0, SRCCOPY);
		dcMem.SelectObject(pOldBmp);
		bmpMem.DeleteObject();
	}
 
	dcMem.DeleteDC();
}
 
BOOL CSWTreeCtrl::OnEraseBkgnd(CDC* pDC)
{
	return TRUE;
}
 
BOOL CSWTreeCtrl::CheckHit(CPoint point)
{
	UINT fFlags;
	HTREEITEM hItem = HitTest(point, &fFlags);
 
	CRect rcItem, rcClient;
	GetClientRect(rcClient);
	GetItemRect(hItem, &rcItem, TRUE);
	if (fFlags & TVHT_ONITEMICON ||
		fFlags & TVHT_ONITEMBUTTON ||
		fFlags & TVHT_ONITEMSTATEICON)
		return TRUE;
 
	if (GetStyle()&TVS_FULLROWSELECT)
	{
		rcItem.right = rcClient.right;
		if (rcItem.PtInRect(point))
			return TRUE;
 
		return FALSE;
	}
 
	if (fFlags & TVHT_ONITEMLABEL)
	{
		rcItem.right = m_nFirstColumnWidth;
		if (!rcItem.PtInRect(point))
			return FALSE;
 
		CString strSub;
		AfxExtractSubString(strSub, GetItemText(hItem), 0, 't');
		CDC* pDC = GetDC();
		pDC->SelectObject(GetFont());
		rcItem.right = rcItem.left   pDC->GetTextExtent(strSub).cx   6;
		ReleaseDC(pDC);
		if (!rcItem.PtInRect(point))
			return FALSE;
 
		return TRUE;
	}
 
	return FALSE;
}

根据节点关联数据值,采用递归算法查找指定节点。

代码语言:javascript复制
HTREEITEM findTreeItem(CTreeCtrl& treeCtrl, HTREEITEM hItem, DWORD dwData) {
	if (treeCtrl.GetItemData(hItem) == dwData)
		return hItem;
 
	if (treeCtrl.ItemHasChildren(hItem)) {
		HTREEITEM hChild = treeCtrl.GetChildItem(hItem);
		HTREEITEM hFindItem = findTreeItem(treeCtrl, hChild, dwData);
		if (hFindItem != NULL)
			return hFindItem;
	}
 
	HTREEITEM hNext = treeCtrl.GetNextSiblingItem(hItem);
	if (hNext) {
		HTREEITEM hFindItem = findTreeItem(treeCtrl, hNext, dwData);
		if (hFindItem != NULL)
			return hFindItem;
		else
			return NULL;
	}
	else {
		return NULL;
	}
};

0 人点赞