Click here to Skip to main content
15,892,768 members
Articles / Desktop Programming / MFC

Custom Drawn Vertical Tree Control

Rate me:
Please Sign up or sign in to vote.
4.93/5 (39 votes)
14 Oct 20053 min read 148.6K   3.3K   107  
A CTreeCtrl derived class which is both: a normal CTreeCtrl or a fully custom drawn vertical tree control
/**
 * Copyright (C) 2005
 * Doga Arinir
 * 
 * Author: Doga Arinir
 * E-Mail: arinir@gmx.de
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the author or the company be held liable 
 * for any damages arising from the use of this software. EXPECT BUGS!
 * 
 * You may use this software in compiled form in any way you desire PROVIDING it is
 * not sold for profit without the authors written permission, and providing that this
 * notice and the authors name is included. If the source code in this file is used in 
 * any commercial application then acknowledgement must be made to the author of this file.
 */

#include "stdafx.h"
#include "VerticalTree.h"
#include "CustomVerticalTree.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CVerticalTree

CVerticalTree::CVerticalTree() : CTreeCtrl(), m_bCustomDrawn(TRUE), m_ItemSpaceX(10), m_ItemSpaceY(250)
{
	m_TreeLinePen.CreatePen(PS_DOT, 1, RGB(120,120,120));

	m_LeafTextColor = RGB(0,0,0);
	m_LeafBkColor = RGB(240, 240, 240);

	m_ItemTextColor = RGB(255, 255, 0);
	m_ItemBkColor = RGB(200, 200, 255);
}

CVerticalTree::~CVerticalTree()
{
	DeleteAllViewportItems();
}
void CVerticalTree::SetCustomDrawn(BOOL c)
{
	m_bCustomDrawn = c;
}
BOOL CVerticalTree::GetCustomDrawn()
{
	return m_bCustomDrawn;
}


BEGIN_MESSAGE_MAP(CVerticalTree, CTreeCtrl)
	//{{AFX_MSG_MAP(CVerticalTree)
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	ON_WM_HSCROLL()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEWHEEL()
	ON_WM_VSCROLL()
	ON_WM_LBUTTONDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_SIZING()
	ON_WM_SIZE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// Behandlungsroutinen f�r Nachrichten CVerticalTree 

void CVerticalTree::OnPaint() 
{
	if (m_bCustomDrawn)
	{
		CPaintDC dc(this);
		//Double Buffering...
		CDC dc_backbuffer;
		CBitmap bb_image;

		//Calculate scoll position and control dimensions
		UpdateDimensions();

		//Create device context for the backbuffer
		dc_backbuffer.CreateCompatibleDC(&dc);
		//Create the image we are going to draw in
		bb_image.CreateCompatibleBitmap(&dc, m_Rect.Width(), m_Rect.Height());
		dc_backbuffer.SelectObject(&bb_image);

		//We use the same Font which was configured in the main device context
		dc_backbuffer.SelectObject(GetFont());
		dc_backbuffer.SetBkMode(TRANSPARENT);

		//General Info
		DWORD style = GetStyle();
		m_bHasButtons = (style & TVS_HASBUTTONS) != 0;
		m_bHasLines = (style & TVS_HASLINES) != 0;

		//Now draw the items...
		m_XMax = 0;
		m_YMax = 0;
		dc_backbuffer.FillSolidRect(m_Rect, dc_backbuffer.GetBkColor()); 
		int xoffset = 0;
		for (HTREEITEM root = GetRootItem(); root != NULL; root = GetNextItem(root, TVGN_NEXT))
			xoffset = DrawItem(&dc_backbuffer, root, xoffset);

		//Copy the backbuffer to the main device context...
		dc.BitBlt( m_Rect.left, m_Rect.top, m_Rect.Width(), m_Rect.Height(), &dc_backbuffer, 0, 0, SRCCOPY);

		ResetScrollbars();
	}
	else
		CTreeCtrl::OnPaint();
}

BOOL CVerticalTree::OnEraseBkgnd(CDC* pDC) 
{
	if (m_bCustomDrawn)
		return TRUE;
	else
		return CTreeCtrl::OnEraseBkgnd(pDC);
}

void CVerticalTree::UpdateDimensions()
{
	GetClientRect(&m_Rect);
	SCROLLINFO scroll_info;
	if (GetScrollInfo(SB_HORZ, &scroll_info, SIF_POS | SIF_RANGE))
	{
		m_OffsetX = -scroll_info.nPos;
	}
	else
	{
		m_OffsetX = m_Rect.left;
	}
	if (GetScrollInfo(SB_VERT, &scroll_info, SIF_POS | SIF_RANGE))
	{
		if ( scroll_info.nMin == 0 && scroll_info.nMax == 100) 
			scroll_info.nMax = 0;

		m_OffsetY = -scroll_info.nPos;
	}
	else
	{
		m_OffsetY = m_Rect.top;
	}
}


void CVerticalTree::DrawItemText(CDC *pDC, HTREEITEM item, const char *text, int x, int y, int cx, int cy, COLORREF bkcolor,COLORREF txtcolor)
{
	pDC->FillSolidRect(x+m_OffsetX, y+m_OffsetY, cx, cy, bkcolor);
	pDC->SetTextColor(txtcolor);
	pDC->TextOut(x+m_OffsetX,y+m_OffsetY,text);


	ItemViewport *nvp = GetViewport(item);
	
	nvp->x = x;
	nvp->y = y;
	nvp->cx = cx;
	nvp->cy = cy;

	if (x + cx > m_XMax)
		m_XMax = x + cx;
	if (y + cy > m_YMax)
		m_YMax = y + cy;

}


int CVerticalTree::DrawItem(CDC *pDC, HTREEITEM item, int x, int level)
{
	CString name = GetItemText(item);
	CSize text_size = pDC->GetTextExtent(name);

	int state = GetItemState(item, TVIF_STATE);
	bool selected = (state & TVIS_SELECTED) != 0;

	if (ItemHasChildren(item))
	{
		int left = x;
		int right = 0;
		int childcount = 0;

		if (state & TVIS_EXPANDED)
			for (HTREEITEM childitem = GetChildItem(item); childitem != NULL; childitem = GetNextItem(childitem, TVGN_NEXT))
			{
				right = DrawItem(pDC, childitem, x, level + 1);
				x = right;
				childcount++;
			}

		right = right - m_ItemSpaceX;
		int width =  right - left;
		x = left + width / 2 - text_size.cx / 2;

		if (x < left + m_ItemSpaceX)
		   x = left;
		

		int y = level*m_ItemSpaceY;
		DrawItemText(pDC, item, name, x, level*m_ItemSpaceY, text_size.cx, text_size.cy, m_ItemBkColor, m_ItemTextColor);

		//Draw lines...
		if (m_bHasLines && (state & TVIS_EXPANDED))
		{
			int xstart = x + text_size.cx / 2;
			int ystart = y + text_size.cy + BUTTON_SIZE;
			CGdiObject *oldPen = pDC->SelectObject(&m_TreeLinePen);
			for (HTREEITEM childitem = GetChildItem(item); childitem != NULL; childitem = GetNextItem(childitem, TVGN_NEXT))
			{
				ItemViewport *current = GetViewport(childitem);
				pDC->MoveTo(xstart+m_OffsetX,ystart+m_OffsetY);
				pDC->LineTo(current->x +m_OffsetX + current->cx / 2 , current->y + m_OffsetY);
			}
			pDC->SelectObject(oldPen);
		}

		if (m_bHasButtons)
		{
			pDC->Draw3dRect(x+m_OffsetX+text_size.cx / 2 - BUTTON_SIZE / 2, y+m_OffsetY + text_size.cy, BUTTON_SIZE, BUTTON_SIZE, RGB(200,200,200), RGB(100,100,100));
			pDC->MoveTo(x+m_OffsetX+text_size.cx / 2 - BUTTON_SIZE / 2 + 2, y+m_OffsetY + text_size.cy + BUTTON_SIZE / 2);
			pDC->LineTo(x+m_OffsetX+text_size.cx / 2 + BUTTON_SIZE / 2 - 1, y+m_OffsetY + text_size.cy + BUTTON_SIZE / 2);

			if ((state & TVIS_EXPANDED) == 0)
			{
				pDC->MoveTo(x+m_OffsetX+text_size.cx / 2, y+m_OffsetY + text_size.cy + 2);
				pDC->LineTo(x+m_OffsetX+text_size.cx / 2, y+m_OffsetY + text_size.cy + BUTTON_SIZE - 2);
			}
		}
		if (right > x + text_size.cx)
		   return right + m_ItemSpaceX;
		else
		   return x + text_size.cx + m_ItemSpaceX;
	}
	else
	{
		DrawItemText(pDC, item, name, x, level*m_ItemSpaceY, text_size.cx, text_size.cy, m_LeafBkColor, m_LeafTextColor);
		return x + text_size.cx + m_ItemSpaceX;
	}
}

void CVerticalTree::SetItemSpaceX(int space)
{
	m_ItemSpaceX = space;
}

int CVerticalTree::GetItemSpaceX()
{
	return m_ItemSpaceX;
}
void CVerticalTree::SetItemSpaceY(int space)
{
	m_ItemSpaceY = space;
}

int CVerticalTree::GetItemSpaceY()
{
	return m_ItemSpaceY;
}

void CVerticalTree::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	if (m_bCustomDrawn)
	{
		int iScrollBarPos = GetScrollPos( SB_HORZ );

		switch( nSBCode )
		{
			case SB_LINEUP:
				iScrollBarPos = max( iScrollBarPos - 1, 0 );
			break;

			case SB_LINEDOWN:
				iScrollBarPos = min( iScrollBarPos + 1, GetScrollLimit( SB_HORZ ) );
			break;

			case SB_PAGEUP:
				iScrollBarPos = max( iScrollBarPos - m_Rect.Width(), 0 );
			break;

			case SB_PAGEDOWN:
				iScrollBarPos = min( iScrollBarPos + m_Rect.Width(), GetScrollLimit( SB_HORZ ) );
			break;

			case SB_THUMBTRACK:
			case SB_THUMBPOSITION:
				iScrollBarPos = nPos;
				break;
		}		

		SetScrollPos( SB_HORZ, iScrollBarPos );
		
		Invalidate();
	}
	else
		CTreeCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
}

void CVerticalTree::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if (m_bCustomDrawn)
	{
		HTREE2VPMAP::iterator it = m_HTree2VP.begin();

		point.x -= m_OffsetX;
		point.y -= m_OffsetY;
		while (it != m_HTree2VP.end())
		{
			ItemViewport *vp = (*it).second;
			if (vp->x <= point.x && ((vp->x+vp->cx) >= point.x)
				&& vp->y <= point.y && ((vp->y+vp->cy) >= point.y))
			{
				Expand(vp->item, TVE_TOGGLE);
				Invalidate();
				return;
			}
			it++;
		}
	}
	else
		CTreeCtrl::OnLButtonUp(nFlags, point);
}

BOOL CVerticalTree::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) 
{
	if (m_bCustomDrawn)
	{
		return FALSE;
	}
	else	
		return CTreeCtrl::OnMouseWheel(nFlags, zDelta, pt);
}

void CVerticalTree::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	if (m_bCustomDrawn)
	{
		int iScrollBarPos = GetScrollPos( SB_VERT );

		switch( nSBCode )
		{
			case SB_LINEUP:
				iScrollBarPos = max( iScrollBarPos - 1, 0 );
			break;

			case SB_LINEDOWN:
				iScrollBarPos = min( iScrollBarPos + 1, GetScrollLimit( SB_VERT ) );
			break;

			case SB_PAGEUP:
				iScrollBarPos = max( iScrollBarPos - m_Rect.Height(), 0 );
			break;

			case SB_PAGEDOWN:
				iScrollBarPos = min( iScrollBarPos + m_Rect.Height(), GetScrollLimit( SB_VERT ) );
			break;

			case SB_THUMBTRACK:
			case SB_THUMBPOSITION:
				iScrollBarPos = nPos;
				break;
		}		

		SetScrollPos( SB_VERT, iScrollBarPos );
		
		Invalidate();
	}
	else
		CTreeCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}




void CVerticalTree::OnLButtonDown(UINT nFlags, CPoint point) 
{
	if (m_bCustomDrawn)
	{
	}
	else	
		CTreeCtrl::OnLButtonDown(nFlags, point);
}

void CVerticalTree::OnMouseMove(UINT nFlags, CPoint point) 
{
	if (m_bCustomDrawn)
	{
	}
	else	
		CTreeCtrl::OnMouseMove(nFlags, point);
}


HTREEITEM CVerticalTree::InsertItem(LPTVINSERTSTRUCT lpInsertStruct)
{
	HTREEITEM htreeitem = CTreeCtrl::InsertItem(lpInsertStruct);
	//I presume that CTreeCtrl::InsertItem won't create same handles,
	//if the key is in use this will lead to memory leaks...
	m_HTree2VP.insert(HTREE2VPMAP::value_type(htreeitem, new ItemViewport(htreeitem)));
	return htreeitem;
}
HTREEITEM CVerticalTree::InsertItem(UINT nMask, LPCTSTR lpszItem, int nImage, int nSelectedImage, UINT nState, UINT nStateMask, LPARAM lParam, HTREEITEM hParent, HTREEITEM hInsertAfter)
{
	HTREEITEM htreeitem = CTreeCtrl::InsertItem(nMask, lpszItem, nImage, nSelectedImage, nState, nStateMask, lParam, hParent, hInsertAfter);
	//I presume that CTreeCtrl::InsertItem won't create same handles,
	//if the key is in use this will lead to memory leaks...
	m_HTree2VP.insert(HTREE2VPMAP::value_type(htreeitem, new ItemViewport(htreeitem)));
	return htreeitem;
}
HTREEITEM CVerticalTree::InsertItem(LPCTSTR lpszItem, HTREEITEM hParent, HTREEITEM hInsertAfter)
{
	HTREEITEM htreeitem = CTreeCtrl::InsertItem(lpszItem, hParent, hInsertAfter);
	//I presume that CTreeCtrl::InsertItem won't create same handles,
	//if the key is in use this will lead to memory leaks...
	m_HTree2VP.insert(HTREE2VPMAP::value_type(htreeitem, new ItemViewport(htreeitem)));
	return htreeitem;
}
HTREEITEM CVerticalTree::InsertItem(LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent, HTREEITEM hInsertAfter)
{
	HTREEITEM htreeitem = CTreeCtrl::InsertItem(lpszItem, nImage, nSelectedImage, hParent, hInsertAfter);
	//I presume that CTreeCtrl::InsertItem won't create same handles,
	//if the key is in use this will lead to memory leaks...
	m_HTree2VP.insert(HTREE2VPMAP::value_type(htreeitem, new ItemViewport(htreeitem)));
	return htreeitem;
}
BOOL CVerticalTree::DeleteItem(HTREEITEM hItem)
{
	BOOL ret = CTreeCtrl::DeleteItem(hItem);
	HTREE2VPMAP::iterator it = m_HTree2VP.find(hItem);
	if (it != m_HTree2VP.end())
	{
		delete (*it).second;
		m_HTree2VP.erase(it);
	}
	return ret;
}
BOOL CVerticalTree::DeleteAllItems()
{
	DeleteAllViewportItems();
	return CTreeCtrl::DeleteAllItems();
}

void CVerticalTree::DeleteAllViewportItems()
{
	HTREE2VPMAP::iterator it = m_HTree2VP.begin();
	while (it != m_HTree2VP.end())
	{
		delete (*it).second;
		it++;
	}

}

__forceinline CVerticalTree::ItemViewport* CVerticalTree::GetViewport(HTREEITEM hItem)
{
	HTREE2VPMAP::iterator it = m_HTree2VP.find(hItem);
	if (it != m_HTree2VP.end())
		return (*it).second;
	return NULL;
}

void CVerticalTree::ResetScrollbars()
{
	CRect rect;
	GetClientRect(rect);

	if(rect.Height() > m_YMax + 8 )
	{
		ShowScrollBar( SB_VERT, FALSE );	// Hide it
		SetScrollPos( SB_VERT, 0 );
	}
	else
	{
		SCROLLINFO	si;
		si.cbSize = sizeof(SCROLLINFO);
		si.fMask = SIF_PAGE | SIF_RANGE;
		si.nPage = rect.Height();
		si.nMax = m_YMax + 8;
		si.nMin = 0 ;

		SetScrollInfo( SB_VERT, &si );
		EnableScrollBarCtrl( SB_VERT, TRUE );
	}


	if(rect.Width() > m_XMax + 8 )
	{
		ShowScrollBar( SB_HORZ, FALSE );	// Hide it
		SetScrollPos( SB_HORZ, 0 );
	}
	else
	{
		SCROLLINFO	si;
		si.cbSize = sizeof(SCROLLINFO);
		si.fMask = SIF_PAGE | SIF_RANGE;
		si.nPage = rect.Width();
		si.nMax = m_XMax + 8;
		si.nMin = 0 ;

		SetScrollInfo( SB_HORZ, &si );
		EnableScrollBarCtrl( SB_HORZ, TRUE );
	}
}

void CVerticalTree::OnSizing(UINT fwSide, LPRECT pRect) 
{
	if (!m_bCustomDrawn)
		CTreeCtrl::OnSizing(fwSide, pRect);
}

void CVerticalTree::OnSize(UINT nType, int cx, int cy) 
{
	if (!m_bCustomDrawn)
		CTreeCtrl::OnSize(nType, cx, cy);
	else
	{
		ResetScrollbars();
		Invalidate();
	}	
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
Chief Technology Officer W3L
Germany Germany
-Since 1th August 2007: Chief Technology Officer of W3L
-2002/08/01-2007/07/31: PhD student
-1997/10/15-2002/07/31: Studied Electrical Engineering and Computer Science

Comments and Discussions