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

ComboTree

Rate me:
Please Sign up or sign in to vote.
4.57/5 (17 votes)
11 May 2003 241.9K   6.9K   93  
ComboBox with a tree control drop down
/* Standard Disclaimer: 
Copyright (C) 2000  Dennis Howard
This file is free software; you can redistribute it and/or
modify it without any conditions. There is no warranty,
implied or expressed, as to validity or fitness for a particular purpose.
*/

// ComboTreeDropList.cpp : implementation file
//

//Tree traversal, search and expand collapse extensions modified from
//http://www.codeguru.com/treeview/index.shtml
// Three state checkbox implementation modified from "Adding Advanced Checkbox"
//http://www.codeguru.com/treeview/advanced_checkbox.shtml

#include "stdafx.h"
#include "ComboTreeDropList.h"
#include "ComboTree.h"
#include "resource.h"

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



/////////////////////////////////////////////////////////////////////////////
// ComboTreeDropList

ComboTreeDropList::ComboTreeDropList()  : m_pCombo (NULL)
{
	m_Delimiter = _T('/');
}

ComboTreeDropList::~ComboTreeDropList()
{
}


BEGIN_MESSAGE_MAP(ComboTreeDropList, CTreeCtrl)
	//{{AFX_MSG_MAP(ComboTreeDropList)
	ON_WM_LBUTTONDBLCLK()
	ON_WM_ACTIVATEAPP()
	ON_WM_GETDLGCODE()
	ON_WM_CREATE()
	ON_WM_LBUTTONDOWN()
	ON_WM_KEYDOWN()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// ComboTreeDropList message handlers


void ComboTreeDropList::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	CTreeCtrl::OnLButtonDblClk(nFlags, point);

	if (m_pCombo)
	{
		m_pCombo->SendParentComboMessage (CBN_DBLCLK);
		m_pCombo->OnSelection ();
	}
}

HTREEITEM ComboTreeDropList::AddString ( LPCTSTR lpszString)
{
	HTREEITEM hRoot = NULL;
	if (!lpszString ||  (_tcsclen (lpszString) == 0) )
	{
		return NULL;
	}

	CString strTreeBranch = lpszString;
	HTREEITEM hEndNode = DropListAddItem (hRoot, strTreeBranch);

	return hEndNode;
}


void ComboTreeDropList::SplitPath (const CString& strTreeBranch, CString& strRoot, CString& strRemainder)
{ 
    int DelimiterPos = strTreeBranch.Find (m_Delimiter);

	strRoot      = _T("");
	strRemainder = _T("");

	if (DelimiterPos == -1)
	{
		strRoot = strTreeBranch;
		return;
	}

	//Separate root substring
	if (DelimiterPos > 0)
	{
		strRoot = strTreeBranch.Mid (0, DelimiterPos);
	}

	//Separate remainder substring
	if (DelimiterPos < strTreeBranch.GetLength ())
	{
	   strRemainder = strTreeBranch.Mid (DelimiterPos + 1);
	}

}

HTREEITEM ComboTreeDropList::MatchSibling (HTREEITEM hItem, CString& strMatch)
{
	CString NodeText;
	HTREEITEM hSibling = NULL;
	if (hItem)
	{
		hSibling = hItem;
		while (hSibling)
		{
			NodeText = GetItemText (hSibling);
			if (NodeText == strMatch)
			{
				break;
			}
			else
			{
				hSibling = GetNextSiblingItem (hSibling);
			}
		}
	}
	return hSibling;
}

HTREEITEM ComboTreeDropList::SelectString( LPCTSTR lpszString, HTREEITEM hParent /*= NULL*/)
{
//	TRACE ("ComboTreeDropList::SelectString()\n");
	HTREEITEM hMatch = FindString (lpszString, hParent);
	if (hMatch)
	{
		SelectItem (hMatch);
	}
	return hMatch;
}


HTREEITEM ComboTreeDropList::FindString (CString strTreeBranch, HTREEITEM hParent /*=NULL*/ )
{
	HTREEITEM hPreviousMatch;
	HTREEITEM hMatch;
	CString strRoot;
	CString strRemainder;

	hMatch = FindString (hParent, strTreeBranch, hPreviousMatch, strRoot, strRemainder);

	if (!strRemainder.IsEmpty ())
	{
		hMatch = NULL;
	}

	return hMatch;
}

HTREEITEM ComboTreeDropList::FindString (HTREEITEM hParent, CString strTreeBranch,
								   HTREEITEM& hPreviousMatch, CString& strRoot, CString& strRemainder )
{
	HTREEITEM hItem = hParent;
	HTREEITEM hMatch = NULL;

	hPreviousMatch = hParent;


	SplitPath (strTreeBranch, strRoot, strRemainder);
	if (strRoot.IsEmpty ())
	{
		//Nothing to search for
		return NULL;
	}

	 //special case when the tree is empty
	 if (hParent == NULL)
	 {
		 hItem = GetChildItem (hParent);
	 }

	 //try to find a child item that matches the substring text
	 //at the corresponding level

	 //Match tree nodes until a node is encountered that doesn't match
	 hMatch = MatchSibling (hItem, strRoot);
	 while (hMatch && !strRemainder.IsEmpty())
	 {
 		hPreviousMatch = hMatch;
		strTreeBranch = strRemainder;
		SplitPath (strTreeBranch, strRoot, strRemainder);
		hItem = GetChildItem (hMatch);
		hMatch = MatchSibling (hItem, strRoot);
	 }

    return hMatch;
}



HTREEITEM ComboTreeDropList::DropListAddItem (HTREEITEM hParent, CString strTreeBranch)
{
 	HTREEITEM hAddedItem = NULL;
	HTREEITEM hPreviousMatch = hParent;
	HTREEITEM hMatch = NULL;

	CString strRemainder;
	CString strRoot;

	if (!m_pCombo)
	{
		return NULL;
	}

	BOOL bHasChecks = m_pCombo->GetHasCheckboxes ();

	hMatch = FindString (hParent, strTreeBranch, hPreviousMatch, strRoot, strRemainder);

	//Add nodes until the remainder is gone
	while (!strRemainder.IsEmpty())
	{
		hPreviousMatch = InsertItem (strRoot, hPreviousMatch);
		if (bHasChecks && hPreviousMatch)
		{
			SetItemState( hPreviousMatch, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK );
		}

		strTreeBranch = strRemainder;
		SplitPath (strTreeBranch, strRoot, strRemainder);
	}
	
	//add only if the node doesn't match an existing node
	if (!hMatch)
	{
		hAddedItem = InsertItem (strRoot, hPreviousMatch);
	}

	if (bHasChecks && hAddedItem)
	{
		SetItemState( hAddedItem, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK );
	}

	return hAddedItem;
}



CString ComboTreeDropList::GetTreePath (HTREEITEM hItem)
{
	CString ItemText;
	CString PathText;
	if (hItem)
	{
		PathText = GetItemText (hItem);
		HTREEITEM hParent = GetParentItem (hItem);
		while (hParent)
		{
			ItemText = GetItemText (hParent);
			PathText = ItemText + m_Delimiter + PathText;
			hParent = GetParentItem (hParent);
		}
	}
	return PathText;
}

CString ComboTreeDropList::GetCurrentTreePath ()
{
	HTREEITEM hItem = GetSelectedItem ();
	
	return GetTreePath (hItem);
}

HTREEITEM ComboTreeDropList::FindChildItemData(DWORD SearchData, HTREEITEM hItem) 
{

	
	if (GetCount () < 1)
	{
		return NULL;
	}

	
	HTREEITEM hCurrent = hItem ? hItem : GetRootItem() ;
	HTREEITEM hChild = NULL;

	DWORD ItemData;

	if ( hItem == NULL)
	{
		hChild = hCurrent;
	}
	else if ( ItemHasChildren( hCurrent ) )
	{
		hChild = GetChildItem( hCurrent );
	}

	while( hChild )
	{
		ItemData = GetItemData( hChild );
		if ( ItemData == SearchData )
		{
			return hChild;
		}
		
		hChild = GetNextSiblingItem( hChild );

	}
	return NULL;
}


HTREEITEM ComboTreeDropList::FindChildItem (LPCTSTR Label, HTREEITEM hItem ) 
{
	if (GetCount () < 1)
	{
		return NULL;
	}
	
	HTREEITEM hCurrent = hItem ? hItem : GetRootItem() ;
	HTREEITEM hChild = NULL;

	CString NodeText;

	if ( hItem == NULL)
	{
		hChild = hCurrent;
	}
	else if ( ItemHasChildren( hCurrent ) )
	{
		hChild = GetChildItem( hCurrent );
	}

	while( hChild )
	{
		NodeText = GetItemText ( hChild );
		if ( NodeText == Label )
		{
			return hChild;
		}
		
		hChild = GetNextSiblingItem( hChild );

	}
	return NULL;
}


// GetLastItem  - Gets last item in the branch
// Returns      - Last item
// hItem        - Node identifying the branch. NULL will 
//                return the last item in outine

HTREEITEM ComboTreeDropList::GetLastItem( HTREEITEM hItem ) 
{
	// Last child of the last child of the last child ...
	HTREEITEM htiNext;
	
	if( hItem == NULL ){
		// Get the last item at the top level
		htiNext = GetRootItem();
		while( htiNext ){
			hItem = htiNext;
			htiNext = GetNextSiblingItem( htiNext );
		}
	}
	
	while( ItemHasChildren( hItem ) ){
		htiNext = GetChildItem( hItem );
		while( htiNext ){
			hItem = htiNext;
			htiNext = GetNextSiblingItem( htiNext );
		}
	}
	
	return hItem;
	
}


HTREEITEM ComboTreeDropList::GetNextItem( HTREEITEM hItem ) 
{
	HTREEITEM hCurrent;
	
	if( ItemHasChildren( hItem ) )
	{
		return GetChildItem( hItem ); // return first child
	}
	else
	{
		// return next sibling item
		// Go up the tree to find a parent's sibling if needed.
		while( (hCurrent = GetNextSiblingItem( hItem )) == NULL )
		{
			if( (hItem = GetParentItem( hItem ) ) == NULL )
			{
				return NULL;
			}
		}
	}
	return hCurrent;
}


HTREEITEM ComboTreeDropList::GetPrevItem( HTREEITEM hItem ) 
{
	 HTREEITEM hCurrent;
	
	 hCurrent = GetPrevSiblingItem(hItem);
	 if( hCurrent == NULL )
	 {
		 hCurrent = GetParentItem(hItem);
	 }
	 else
	 {
		 hCurrent = GetLastItem(hCurrent);
	 }
	 return hCurrent;
}


// ExpandBranch - Expands a branch completely
// hItem - Handle of the tree item to expand
void ComboTreeDropList::ExpandBranch( HTREEITEM hItem ) 
{
	if( ItemHasChildren( hItem ) )
	{
		Expand( hItem, TVE_EXPAND );
		hItem = GetChildItem( hItem );
		do
		{
			ExpandBranch( hItem );
		}while( (hItem = GetNextSiblingItem( hItem )) != NULL );
	}
	EnsureVisible( GetSelectedItem() );
}


// CollapseBranch - Collapses a branch completely
// hItem - Handle of the tree item to collapse
void ComboTreeDropList::CollapseBranch( HTREEITEM hItem) 
{
	if( ItemHasChildren( hItem ) )
	{
		Expand( hItem, TVE_COLLAPSE );
		hItem = GetChildItem( hItem );
		do
		{
			CollapseBranch( hItem );
		} while( (hItem = GetNextSiblingItem( hItem )) != NULL );
	}
}

// GetLastSibling - return last sibling of node
// hItem - Handle of the tree item
HTREEITEM ComboTreeDropList::GetLastSibling( HTREEITEM hItem ) 
{

	ASSERT (hItem != NULL);

	HTREEITEM hLastSibling = hItem;	
	// return next sibling item
	while( (hItem = GetNextSiblingItem( hItem )) == NULL )
	{
		hLastSibling = hItem;

	}
	return hItem;
}

// CollapseAllSiblings - collapses all sibling nodes of the tree
// hItem - Handle of the tree item
void ComboTreeDropList::CollapseAllSiblings( HTREEITEM hNode )
{

	HTREEITEM hSiblingItem;
	HTREEITEM hItem = hNode;
	if( hItem)
	{
		hSiblingItem = GetNextSiblingItem (hItem);
		while (hSiblingItem != NULL)
		{
			Expand (hSiblingItem, TVE_COLLAPSE);
			hSiblingItem = GetNextSiblingItem (hSiblingItem);
		}

		hSiblingItem = GetPrevSiblingItem (hItem);
		while (hSiblingItem != NULL)
		{
			Expand (hSiblingItem, TVE_COLLAPSE);
			hSiblingItem = GetPrevSiblingItem (hSiblingItem);
		}

	}

}



// SetCheck	- Check, uncheck, toggle or refresh an item
// hItem	- Item that is to be updated
// nCheck	- CHECK, UNCHECK, TOGGLE OR REFRESH 
BOOL ComboTreeDropList::SetCheck( HTREEITEM hItem, CheckType nCheck )
{

	//can't call this tree has no checkboxes
	if (!(m_pCombo && m_pCombo->GetHasCheckboxes ()))
	{
		_ASSERTE(("Tree does not have checkboxes enabled!", FALSE));
		return FALSE;
	}

	
	if( hItem == NULL ) 
		return FALSE;

	BOOL bNotify = FALSE;

	UINT nState = GetItemState( hItem, TVIS_STATEIMAGEMASK ) >> 12;

	if( nCheck == TOGGLE )
	{
		bNotify = TRUE;
		switch( nState )
		{
		case UNCHECKED:	
			nState = CHECKED;
			break;
		case CHECKED:
		case CHILD_CHECKED:
			nState = UNCHECKED;
			break;
		}
	}
	else if( nCheck == REFRESH )
	{
		// Match child state to current state
		if (((nState == CHECKED) || (nState == UNCHECKED)) && ItemHasChildren (hItem))
		{
			SetChildState( hItem, nState );
		}
		
		//Match Parent state to current state
		if ( GetParentItem( hItem ) != NULL )
		{
			SetParentState (hItem);
		}

	}
	else if (nCheck == CHECK)
	{
		bNotify = TRUE;
		nState = CHECKED;
	}
	else if (nCheck == UNCHECK)
	{
		bNotify = TRUE;
		nState = UNCHECKED;
	}

	SetItemState( hItem, INDEXTOSTATEIMAGEMASK(nState), 
					TVIS_STATEIMAGEMASK );

	if (( nState == CHECKED) || ( REFRESH && (nState == CHILD_CHECKED)))
	{	

		// Mark the child notes to match if state is checked
		if (ItemHasChildren (hItem) && (nState == CHECKED) )
		{
			SetChildState( hItem, CHECKED );
		}
		
		// Mark the parents to indicate that a child item is selected.
		// Use checkbox with red border.
		if ( GetParentItem( hItem ) != NULL )
		{
			SetParentState (hItem);
		}
	}
	else if( nState == UNCHECKED )
	{
		// Mark the child notes to match
		if (ItemHasChildren (hItem))
		{
			SetChildState( hItem, UNCHECKED );
		}

		// Maybe the parent ( ancestors ) state needs to be adjusted if
		// no more children selected.
		if ( GetParentItem( hItem ) != NULL )
		{
			SetParentState (hItem);
		}
	}

	if (m_pCombo && bNotify)
	{
		m_pCombo->SendParentComboMessage (NOTIFY_TREECOMBO_CHECK);
	}


	return TRUE;
}

// SetChildState - Sets the state of all items in a branch
// hItem - Handle of the tree item to set state
void ComboTreeDropList::SetChildState( HTREEITEM hItem, UINT nState )
{
	if( ItemHasChildren( hItem ) )
	{
		SetItemState( hItem,
			INDEXTOSTATEIMAGEMASK(nState), 
			TVIS_STATEIMAGEMASK );
		hItem = GetChildItem( hItem );
		do
		{
			SetChildState( hItem, nState );
		}while( (hItem = GetNextSiblingItem( hItem )) != NULL );
	}
	else
	{
		SetItemState( hItem,
			INDEXTOSTATEIMAGEMASK(nState), 
			TVIS_STATEIMAGEMASK );
	}
}

// Set state of higher node state to reflect new state of child node
void ComboTreeDropList::SetParentState(HTREEITEM hItem) 
{
		while( (hItem = GetParentItem( hItem )) != NULL )
		{
			BOOL bChildSelected = FALSE;
			BOOL bAllChildrenSelected = TRUE;

			HTREEITEM hChild = GetChildItem( hItem );

			while( hChild )
			{
				UINT nChildState = GetItemState( hChild, TVIS_STATEIMAGEMASK ) >> 12;
		
				if( nChildState == CHECKED || nChildState == CHILD_CHECKED )
				{
					bChildSelected = TRUE;
				}

				if (nChildState != CHECKED )
				{
					bAllChildrenSelected = FALSE;
				}
				
				hChild = CTreeCtrl::GetNextItem( hChild, TVGN_NEXT );
			}

			
			if (bAllChildrenSelected)
			{
				//All children are selected, set check to normal check
				SetItemState( hItem, INDEXTOSTATEIMAGEMASK(CHECKED), 
						TVIS_STATEIMAGEMASK );

			}
			else if ( bChildSelected ) 
			{
				//Some child nodes are selected, set state to mixed check state
				SetItemState( hItem, INDEXTOSTATEIMAGEMASK(CHILD_CHECKED), 
						TVIS_STATEIMAGEMASK );

			}
			else
			{
				SetItemState( hItem, 
						INDEXTOSTATEIMAGEMASK(UNCHECKED), 
						TVIS_STATEIMAGEMASK );
			}
		}
}

BOOL ComboTreeDropList::IsItemChecked(HTREEITEM hItem)
{
	//can't call this tree if has no checkboxes
	if (!(m_pCombo && m_pCombo->GetHasCheckboxes ()))
	{
		_ASSERTE(("Tree does not have checkboxes enabled!", FALSE));
		return FALSE;
	}

	int iImage = GetItemState( hItem, TVIS_STATEIMAGEMASK )>>12;
	return iImage == CHECKED;
}

HTREEITEM ComboTreeDropList::GetFirstCheckedItem()
{
	//can't call this tree if has no checkboxes
	if (!(m_pCombo && m_pCombo->GetHasCheckboxes ()))
	{
		_ASSERTE(("Tree does not have checkboxes enabled!", FALSE));
		return FALSE;
	}

	for ( HTREEITEM hItem = GetRootItem(); hItem!=NULL;  )
	{
		int iImage = GetItemState( hItem, TVIS_STATEIMAGEMASK )>>12;
		if ( iImage == CHECKED)
			return hItem;
		
		if( iImage != CHILD_CHECKED )
		{
			HTREEITEM hti = CTreeCtrl::GetNextItem( hItem, TVGN_NEXT );
			if( hti == NULL )
				hItem = GetNextItem( hItem );
			else 
				hItem = hti;
		}
		else
			hItem = GetNextItem( hItem );
	}

	return NULL;
}

HTREEITEM ComboTreeDropList::GetNextCheckedItem( HTREEITEM hItem )
{
	//can't call this tree if has no checkboxes
	if (!(m_pCombo && m_pCombo->GetHasCheckboxes ()))
	{
		_ASSERTE(("Tree does not have checkboxes enabled!", FALSE));
		return NULL;
	}

	hItem = GetNextItem( hItem );
	while( hItem!=NULL )
	{
		int iImage = GetItemState( hItem, TVIS_STATEIMAGEMASK )>>12;
		if ( iImage == CHECKED )
			return hItem;
		
		if( iImage != CHILD_CHECKED )
		{
			HTREEITEM hti = CTreeCtrl::GetNextItem( hItem, TVGN_NEXT );
			if( hti == NULL )
				hItem = GetNextItem( hItem );
			else 
				hItem = hti;
		}
		else
			hItem = GetNextItem( hItem );
	}

	return NULL;
}

HTREEITEM ComboTreeDropList::GetPrevCheckedItem( HTREEITEM hItem )
{
	//can't call this tree if has no checkboxes
	if (!(m_pCombo && m_pCombo->GetHasCheckboxes ()))
	{
		_ASSERTE(("Tree does not have checkboxes enabled!", FALSE));
		return NULL;
	}

	for ( hItem = GetPrevItem( hItem ); hItem!=NULL; hItem = GetPrevItem( hItem ) )
		if ( IsItemChecked(hItem) )
			return hItem;

	return NULL;
}

void ComboTreeDropList::OnActivateApp(BOOL bActive, HTASK hTask) 
{
	CTreeCtrl::OnActivateApp(bActive, hTask);
	
	if (m_pCombo && m_pCombo->GetDroppedState ())
	{
//		TRACE("ComboTreeDropList::OnActivateApp(): bActive: %d\n", bActive);
		m_pCombo->SendParentComboMessage (CBN_KILLFOCUS);
		m_pCombo->OnCancel();
	}
	
}



UINT ComboTreeDropList::OnGetDlgCode() 
{
	UINT result = CTreeCtrl::OnGetDlgCode();
	result = DLGC_WANTALLKEYS | DLGC_WANTARROWS | DLGC_WANTCHARS ;
	return result;
}


int ComboTreeDropList::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CTreeCtrl::OnCreate(lpCreateStruct) == -1)
		return -1;

	if (!m_pCombo)
	{
		return -1;
	}

	m_ToolTip.Init (this);


	if (m_pCombo && m_pCombo->GetHasCheckboxes ())
	{
		BOOL bCreate = m_imageState.Create( _T("COMBOTREECHECKS"), 13, 1, RGB(255,255,255) );
		if (bCreate)
		{
			SetImageList( &m_imageState, TVSIL_STATE );
		}
		else
		{
			TCHAR* errMsg = _T("Can't create tree check image list! Make sure bitmap with \"COMBOTREECHECKS\" (note quotes) was added as resource");
			MessageBox (errMsg);
			return -1;
		}
	}

	return 0;
}



void ComboTreeDropList::OnLButtonDown(UINT nFlags, CPoint point) 
{

	UINT uFlags=0;
	HTREEITEM hItem = HitTest(point,&uFlags);

	if( uFlags & TVHT_ONITEMSTATEICON )
	{
		SetCheck( hItem, TOGGLE );

		return;
	}
	else if (uFlags & TVHT_ONITEM) 
	{
		HTREEITEM hCurSelItem = GetSelectedItem ();
		if (m_pCombo && (hItem != hCurSelItem))
		{
			m_pCombo->SendParentComboMessage (CBN_SELCHANGE);
		}
	}

	CTreeCtrl::OnLButtonDown(nFlags, point);
}

void ComboTreeDropList::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	HTREEITEM hCurSelItem = GetSelectedItem ();

	if( nChar == VK_SPACE )
	{
		HTREEITEM hItem = GetSelectedItem();
		SetCheck( hItem, TOGGLE );
		return;
	}

	CTreeCtrl::OnKeyDown(nChar, nRepCnt, nFlags);

	HTREEITEM hNewSelItem = GetSelectedItem ();
	if (m_pCombo && (hNewSelItem != hCurSelItem))
	{
		m_pCombo->SendParentComboMessage (CBN_SELCHANGE);
	}


}

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.

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
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions