Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / MFC

A TreeList Control

Rate me:
Please Sign up or sign in to vote.
3.86/5 (18 votes)
1 Dec 19993 min read 400.9K   11.3K   80   89
A tree control / list control hybrid

Sample Image - treelist.jpg

The Treelist control is a combination Tree/List control derived from CTreeCtrl.

Here's the description of the classes that are used:

  • CTLFrame - derived from CWnd, this class is the frame class for the treelist control. It is used to include the header control, the tree itself, and the horizontal scroll bar.
  • CNewHeaderCtrl - derived from CHeaderCtrl. Used as the header in the TreeList.
  • CNewTreeListCtrl - derived from CTreeCtrl, used as the main tree in the TreeList.
  • CTLItem - represents each item inside the tree.
  • SSortType - structure that's used to indicate whether the sort of the tree is in an ascending order or descending, and which column is being sorted.

How to Insert TreeList into Your Project?

  1. Insert the following files into your project:
    • TLFrame.cpp, TLFrame.h
    • NewTreeListCtrl.cpp, NewTreeListCtrl.h
    • NewHeaderCtrl.cpp, NewHeaderCtrl.h
  2. Include the file "TLFrame.h" in the app file (where the InitInstance function is) and insert the following line in the InitInstance function:
    C++
    ....
    CTLFrame::RegisterClass();
    ....
  3. Layout a user-defined control inside the dialog into which the control is supposed to be inserted. In the class field type: "LANTIVTREELISTCTRL"
  4. Include "TLFrame.h" in the dialog's header file, and add a member variable: CTLFrame m_wndMyTreeList;
  5. In your OnInitDialog() or OnCreate() functions, subclass the control:
    C++
    ....
    m_wndMyTreeList.SubclassDlgItem(IDC_TREE_LIST, this);
    
    // IDC_TREE_LIST is the ID of the user-defined control you 
    // inserted into the dialog
    ....

That's it !

Using the TreeListCtrl

The use of the control is simple. It's a tree, so treat it as one (HTREEITEM, etc.). In addition, there are functions like InsertColumn, SetItemText, SetItemColor, SetItemBold, GetItemText.

Implementation

Here's my approach to implementing the TreeList. First of all, I had to create a frame window, that would include the 3 objects: header, tree, horz scroll bar. That's what CTLFrame is for. In addition, this CWnd derived class helps during the scroll: the header is clipped after it's repositioned, so there's a feeling of scrolling.

The class CNewHeaderCtrl was created only to put the 3D triangles in it. I decided to include sorting since it's needed in 9/10 cases, and it's a shame every programmer needs to insert it by himself. Finally, CNewTreeListCtrl is the more complicated part. Every item inside the tree, has a DWORD data associated with it, that stores a pointer to a CTLItem class. CTLItem stores the information about each item- its columns' strings, whether the item is bold, the item's color, and the item data that the user wishes to associate with the item. All the functions that deal with the items, like InsertItem, DeleteItem, SetItemText, etc. were overridden in order to use the CTLItem technique.

I had some difficulties with the scrolling part, since there are many different conditions for the scroll bars: vertical scroll is shown and then the horz becomes shorter, it's hidden so the horz should become larger again; what happens if the users changed some column's size... and problems like that. I believe I handle each of these cases so I think there won't be any problems with this part.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Web Developer
Israel Israel

Comments and Discussions

 
QuestionLicense used with this code Pin
Alexander Sailer2-Dec-16 4:46
Alexander Sailer2-Dec-16 4:46 
SuggestionCTLItem::SetSubstring(int m_nSub, CString m_sText) Pin
heylo13-Jun-16 8:33
heylo13-Jun-16 8:33 
Generalwhy i translate this ctrl to OCX(MFC activex)projecthave some error Pin
LGEKS2-Jun-11 0:41
LGEKS2-Jun-11 0:41 
GeneralMy vote of 4 Pin
MANISH RASTOGI2-Mar-11 19:18
MANISH RASTOGI2-Mar-11 19:18 
GeneralNot getting notifications under VS2010 Pin
SaintNeil9-Jun-10 12:09
SaintNeil9-Jun-10 12:09 
Generaldemo is not working buddy !!!!!!!!!!!!! Pin
RAJKUMARV17-Sep-09 23:15
RAJKUMARV17-Sep-09 23:15 
GeneralEditlable Pin
markman10115-Jul-09 0:03
markman10115-Jul-09 0:03 
GeneralBug in sort algorithm Pin
Freak3026-Jan-09 23:17
Freak3026-Jan-09 23:17 
Questionhow to insert a root element after or before an existing one Pin
AnilBharatharajan22-Sep-08 4:24
AnilBharatharajan22-Sep-08 4:24 
GeneralUseless code Pin
kilt4-May-08 7:23
kilt4-May-08 7:23 
GeneralRe: Useless code Pin
Michael O'Rourke1-Apr-09 5:02
Michael O'Rourke1-Apr-09 5:02 
GeneralI found a new bug and fixed it. Pin
newmetic10-Oct-07 20:55
newmetic10-Oct-07 20:55 
GeneralBug fix for CNewTreeListCtrl::OnPaint() Pin
flippydeflippydebop23-Mar-07 20:53
flippydeflippydebop23-Mar-07 20:53 
GeneralRe: Bug fix for CNewTreeListCtrl::OnPaint() [modified] Pin
Kindy Chen23-Sep-07 23:14
Kindy Chen23-Sep-07 23:14 
GeneralMultiple Row Selection Pin
flippydeflippydebop23-Mar-07 20:49
flippydeflippydebop23-Mar-07 20:49 
I have added multiple row selection to this control, and thought i would share it with everyone.

In the file NewTreeListCtrl.h, add the following lines to the class CNewTreeListCtrl

<br />
private:<br />
	void ClearSelection();<br />
	BOOL SelectItems(HTREEITEM hItemFrom, HTREEITEM hItemTo);<br />
<br />
	HTREEITEM GetPrevItem( HTREEITEM hItem );<br />
	HTREEITEM GetLastItem( HTREEITEM hItem );<br />
	HTREEITEM GetNextItem( HTREEITEM hItem );<br />
protected:<br />
	HTREEITEM m_hItemFirstSel;<br />


then in the NewTreeListCtrl.cpp file add:

<br />
HTREEITEM CNewTreeListCtrl::GetFirstSelectedItem()<br />
{<br />
	HTREEITEM hItem;<br />
	for ( hItem = GetRootItem(); hItem!=NULL; hItem = GetNextItem( hItem) )//, TVGN_NEXT ) )<br />
		if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )<br />
			return hItem;<br />
<br />
	return NULL;<br />
}<br />
<br />
HTREEITEM CNewTreeListCtrl::GetNextSelectedItem( HTREEITEM hItem )<br />
{<br />
	for ( hItem = GetNextItem( hItem ); hItem!=NULL; hItem = GetNextItem( hItem ) )<br />
		if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )<br />
			return hItem;<br />
<br />
	return NULL;<br />
}<br />
<br />
HTREEITEM CNewTreeListCtrl::GetPrevSelectedItem( HTREEITEM hItem )<br />
{<br />
	for ( hItem = GetPrevItem( hItem ); hItem!=NULL; hItem = GetPrevItem( hItem ) )<br />
		if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )<br />
			return hItem;<br />
<br />
	return NULL;<br />
}<br />
<br />
HTREEITEM CNewTreeListCtrl::GetLastItem( HTREEITEM hItem )<br />
{<br />
	//	Temporary used variable<br />
	HTREEITEM	htiNext;<br />
<br />
	if( hItem == NULL ) {<br />
		// Get the last item at the top level<br />
		hItem = GetRootItem();<br />
	}<br />
<br />
	while( ItemHasChildren( hItem ) != NULL ) {<br />
		//	Find the last child of hItem<br />
		htiNext = GetChildItem( hItem );<br />
		while( htiNext != NULL ) {<br />
			hItem = htiNext;<br />
			htiNext = GetNextSiblingItem( htiNext );<br />
		}<br />
	}<br />
<br />
	return hItem;<br />
<br />
}<br />
<br />
<br />
HTREEITEM CNewTreeListCtrl::GetNextItem( HTREEITEM hItem )<br />
{<br />
	HTREEITEM hti = NULL;<br />
<br />
	if (ItemHasChildren(hItem))<br />
		hti = GetChildItem(hItem);<br />
<br />
	if (hti == NULL) {<br />
		while ((hti = GetNextSiblingItem(hItem)) == NULL) {<br />
			if ((hItem = GetParentItem(hItem)) == NULL)<br />
				return NULL;<br />
		}<br />
	}<br />
	return hti;<br />
}<br />
<br />
HTREEITEM CNewTreeListCtrl::GetPrevItem( HTREEITEM hItem )<br />
{<br />
	HTREEITEM       hti;<br />
<br />
	hti = GetPrevSiblingItem(hItem);<br />
	if( hti == NULL )<br />
		hti = GetParentItem(hItem);<br />
	else<br />
		hti = GetLastItem(hti);<br />
	return hti;<br />
}<br />
<br />
// SelectItems	- Selects items from hItemFrom to hItemTo. Does not<br />
//		- select child item if parent is collapsed. Removes<br />
//		- selection from all other items<br />
// hItemFrom	- item to start selecting from<br />
// hItemTo	- item to end selection at.<br />
BOOL CNewTreeListCtrl::SelectItems(HTREEITEM hItemFrom, HTREEITEM hItemTo)<br />
{<br />
	HTREEITEM hItem = GetRootItem();<br />
<br />
	// Clear selection upto the first item<br />
	while ( hItem && hItem!=hItemFrom && hItem!=hItemTo )<br />
	{<br />
		hItem = GetNextVisibleItem( hItem );<br />
		SetItemState( hItem, 0, TVIS_SELECTED );<br />
	}<br />
<br />
	if ( !hItem )<br />
		return FALSE;	// Item is not visible<br />
<br />
	SelectItem( hItemTo );<br />
<br />
	// Rearrange hItemFrom and hItemTo so that hItemFirst is at top<br />
	if( hItem == hItemTo )<br />
	{<br />
		hItemTo = hItemFrom;<br />
		hItemFrom = hItem;<br />
	}<br />
<br />
<br />
	// Go through remaining visible items<br />
	BOOL bSelect = TRUE;<br />
	while ( hItem )<br />
	{<br />
		// Select or remove selection depending on whether item<br />
		// is still within the range.<br />
		SetItemState( hItem, bSelect ? TVIS_SELECTED : 0, TVIS_SELECTED );<br />
<br />
		// Do we need to start removing items from selection<br />
		if( hItem == hItemTo )<br />
			bSelect = FALSE;<br />
<br />
		hItem = GetNextVisibleItem( hItem );<br />
	}<br />
<br />
	return TRUE;<br />
}<br />
<br />
void CNewTreeListCtrl::ClearSelection()<br />
{<br />
	// This can be time consuming for very large trees <br />
	// and is called every time the user does a normal selection<br />
	// If performance is an issue, it may be better to maintain <br />
	// a list of selected items<br />
	for ( HTREEITEM hItem=GetRootItem(); hItem!=NULL; hItem=GetNextItem( hItem ) )<br />
		if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )<br />
			SetItemState( hItem, 0, TVIS_SELECTED );<br />
}<br />
<br />


Then add the OnKey handler to the class:

<br />
void CNewTreeListCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) <br />
{<br />
	if ( (nChar==VK_UP || nChar==VK_DOWN) && GetKeyState( VK_SHIFT )&0x8000)<br />
	{<br />
		// Initialize the reference item if this is the first shift selection<br />
		if( !m_hItemFirstSel )<br />
		{<br />
			m_hItemFirstSel = GetSelectedItem();<br />
			ClearSelection();<br />
		}<br />
<br />
		// Find which item is currently selected<br />
		HTREEITEM hItemPrevSel = GetSelectedItem();<br />
<br />
		HTREEITEM hItemNext;<br />
		if ( nChar==VK_UP )<br />
			hItemNext = GetPrevVisibleItem( hItemPrevSel );<br />
		else<br />
			hItemNext = GetNextVisibleItem( hItemPrevSel );<br />
<br />
		if ( hItemNext )<br />
		{<br />
			// Determine if we need to reselect previously selected item<br />
			BOOL bReselect =<br />
				!( GetItemState( hItemNext, TVIS_SELECTED ) & TVIS_SELECTED );<br />
<br />
			// Select the next item - this will also deselect the previous item<br />
			SelectItem( hItemNext );<br />
<br />
			// Reselect the previously selected item<br />
			if ( bReselect )<br />
				SetItemState( hItemPrevSel, TVIS_SELECTED, TVIS_SELECTED );<br />
		}<br />
		return;<br />
	}<br />
	else if( nChar >= VK_SPACE )<br />
	{<br />
		m_hItemFirstSel = NULL;<br />
		ClearSelection();<br />
	}<br />
	CTreeCtrl::OnKeyDown(nChar, nRepCnt, nFlags);<br />
	ResetVertScrollBar();<br />
}<br />



Finally when you come to parsing the selected rows from your dialog you can use this code.



<br />
	HTREEITEM hItem = treeListCtrl_.m_tree.GetFirstSelectedItem();<br />
<br />
	if ( hItem == NULL )<br />
		return;<br />
<br />
	while ( true )<br />
	{<br />
		hItem = treeListCtrl_.m_tree.GetNextSelectedItem( hItem );<br />
<br />
		if ( hItem != NULL )<br />
		{<br />
			std::string text = treeListCtrl_.m_tree.GetItemText(hItem, 0); <br />
		}<br />
		else<br />
			break;<br />
	}<br />

Questionclass field type: "LANTIVTREELISTCTRL" ??? Pin
germs131312-Mar-07 7:02
germs131312-Mar-07 7:02 
GeneralSunken frame Pin
805Coder21-Feb-07 6:43
805Coder21-Feb-07 6:43 
QuestionGood!! ... in C#? Pin
anderslundsgard11-Jul-06 20:33
anderslundsgard11-Jul-06 20:33 
GeneralCatching messages (e.g. TVN_GETDISPINFO) Pin
BenGEAR16-Mar-06 2:04
BenGEAR16-Mar-06 2:04 
GeneralCopyright issues to use this control Pin
padmawar6-Feb-06 10:41
padmawar6-Feb-06 10:41 
GeneralWhy a Assert failure when I use the CHeaderCtrl instead of CNewHeaderCtrl Pin
zhou_wz25-Aug-05 17:16
zhou_wz25-Aug-05 17:16 
GeneralRe: Why a Assert failure when I use the CHeaderCtrl instead of CNewHeaderCtrl Pin
David Cader7-Nov-05 8:32
David Cader7-Nov-05 8:32 
GeneralEditLabel Pin
mungflesh3-Aug-05 23:10
mungflesh3-Aug-05 23:10 
QuestionHow to get .NET assembly of this? Pin
Member 192519730-Apr-05 2:19
Member 192519730-Apr-05 2:19 
GeneralBug in CNewTreeListCtrl::SetItemText() Pin
rimoraj20-Jan-05 21:41
rimoraj20-Jan-05 21:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.