Adding Advanced Checkbox


In the previous topic we covered how to implement a simple checkbox for each item in the tree control. In this topic we cover a slightly different implementation. The checkbox for each item will indicate whether the item is checked or not and it will also indicate whether any of its descendant is checked or not. The main reason for visually indicating whether a descendant is checked is that it is easier for the user to determine if there are checked items in the outline and if there is a checked item, it is easy to navigate to it.

Step 1: Create bitmap with checkbox images

Create a bitmap that will provide the images needed for the checkbox. The images in the bitmap below are 13x13 pixels. The first image is blank since this bitmap is going to be used for the state image list. The next image indicates an unchecked item. The third image indicates a checked item. The fourth image indicates an unchecked item but conveys that at least one of the descendant item is checked. The fifth image indicates a checked item and also indicates that at least one of the descendant item is checked.


Step 2: Initialize the state image list

Setting up state images has been covered in a previous topic. If the tree control class has a member variable m_imageState then here's how to set the image list.
	m_tree.m_imageState.Create( IDB_STATE, 13, 1, RGB(255,255,255) );
	m_tree.SetImageList( &(m_tree.m_imageState), TVSIL_STATE );

Step 3: Insert items with the checkbox as a state image

If you are using TV_INSERTSTRUCT to insert an item into the tree control, then specify the state and the stateMask in the TV_ITEM member. The index of the state image is specified by using the macro INDEXTOSTATEIMAGEMASK. You can also use the SetItemState() function.

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

Step 4: Add enumeration for check operation and state

Since we are dealing with four different check states, it makes the code more readable if we define an enumeration for the different states. We also define an enumeration to list the different operations on the check state.
// Attributes
public:
	enum CheckState{ NOSTATE = 0, UNCHECKED, CHECKED, CHILD_CHECKED, 
		SELF_AND_CHILD_CHECKED };
	enum CheckType{ CHECK, UNCHECK, TOGGLE, REFRESH };

Step 5: Define SetCheck() function

When we check an item, the parent items are also updated to indicate that a descendant is checked. Similarly, when we uncheck an item, the parents item have to be updated again. We may also need to update the parents state if an item is moved.

Depending on the value of nCheck, the function determines what the new state of the item should be. If nCheck is REFRESH, only the immediate children items are scanned to determine if the checkbox should be red ( to indicate that a child is checked ). The function then updates the parent items.

// SetCheck	- Check, uncheck, toggle or refresh an item
// hItem	- Item that is to be updated
// nCheck	- CHECK, UNCHECK, TOGGLE OR REFRESH 
BOOL CTreeCtrlX::SetCheck( HTREEITEM hItem, CheckType nCheck )
{
	if( hItem == NULL ) 
		return FALSE;

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

	// Determine the new check state
	if ( nCheck == CHECK )
	{
		if( nState == CHECKED || nState == SELF_AND_CHILD_CHECKED )
			return TRUE;
		switch( nState )
		{
		case UNCHECKED:		nState = CHECKED; break;
		case CHILD_CHECKED:		nState = SELF_AND_CHILD_CHECKED; break;
		}
	}
	else if( nCheck == UNCHECK )
	{
		if( nState == UNCHECKED || nState == CHILD_CHECKED )
			return TRUE;
		switch( nState )
		{
		case CHECKED:			nState = UNCHECKED; break;
		case SELF_AND_CHILD_CHECKED:	nState = CHILD_CHECKED; break;
		}
	}
	else if( nCheck == TOGGLE )
	{
		switch( nState )
		{
		case UNCHECKED:		nState = CHECKED; break;
		case CHECKED:			nState = UNCHECKED; break;
		case CHILD_CHECKED:		nState = SELF_AND_CHILD_CHECKED; break;
		case SELF_AND_CHILD_CHECKED:	nState = CHILD_CHECKED; break;
		}
	}
	else if( nCheck == REFRESH )
	{
		// Scan children to determine new state
		BOOL bChildSelected = FALSE;
		HTREEITEM htiChild = GetChildItem( hItem );

		// Scan children
		while( htiChild )
		{
			UINT nChildState = GetItemState( htiChild, 
						TVIS_STATEIMAGEMASK ) >> 12;
			if( nChildState != UNCHECKED && nChildState != NOSTATE )
			{
				bChildSelected = TRUE;
				break;
			}
			htiChild = GetNextItem( htiChild, TVGN_NEXT );
		}
		if( bChildSelected )
		{
			if( nState == CHECKED ) nState = SELF_AND_CHILD_CHECKED;
			else if( nState == UNCHECKED ) nState = CHILD_CHECKED;
		}
		else
		{
			if( nState == SELF_AND_CHILD_CHECKED ) nState = CHECKED;
			else if( nState == CHILD_CHECKED ) nState = UNCHECKED;
		}
	}

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

	if( nState == CHECKED 
		|| ( REFRESH 
			&& (nState == SELF_AND_CHILD_CHECKED 
				|| nState == CHILD_CHECKED)
			)
		)
	{
		// Mark the parents to indicate that a child item is selected.
		// Use checkbox with red border.
		while( (hItem = GetParentItem( hItem )) != NULL )
		{
			nState = GetItemState( hItem, TVIS_STATEIMAGEMASK ) >> 12;
			if( nState == UNCHECKED )
				SetItemState( hItem, INDEXTOSTATEIMAGEMASK(CHILD_CHECKED), 
						TVIS_STATEIMAGEMASK );
			else if ( nState == CHECKED )
				SetItemState( hItem,
					INDEXTOSTATEIMAGEMASK(SELF_AND_CHILD_CHECKED), 
					TVIS_STATEIMAGEMASK );
		}
	}
	else if( nState == UNCHECKED )
	{
		// Maybe the parent ( ancestors ) state needs to be adjusted if
		// no more children selected.
		while( (hItem = GetParentItem( hItem )) != NULL )
		{
			BOOL bChildSelected = FALSE;
			HTREEITEM htiChild = GetChildItem( hItem );

			// Scan children
			while( htiChild )
			{
				UINT nChildState = GetItemState( htiChild, 
							TVIS_STATEIMAGEMASK ) >> 12;
				if( nChildState != UNCHECKED && nChildState != NOSTATE )
				{
					bChildSelected = TRUE;
					break;
				}
				htiChild = GetNextItem( htiChild, TVGN_NEXT );
			}
			if( bChildSelected )
			{
				// The parent does not need to be updated
				// since there are other children still selected
				break;
			}
			else
			{
				// The parent item has no more children selected. 
				// Mark accordingly
				UINT nParentState = GetItemState( hItem, 
							TVIS_STATEIMAGEMASK ) >> 12;
				if( nParentState == CHILD_CHECKED )
					SetItemState( hItem, 
							INDEXTOSTATEIMAGEMASK(UNCHECKED), 
							TVIS_STATEIMAGEMASK );
				else if ( nParentState == SELF_AND_CHILD_CHECKED )
					SetItemState( hItem, INDEXTOSTATEIMAGEMASK(CHECKED), 
							TVIS_STATEIMAGEMASK );
			}
		}
 	}                                                                                                              
	return TRUE;
}

Step 6: Add code to OnLButtonDown to toggle checkbox

When the user clicks on the checkbox, then the checkbox state has to be toggled. We use the HitTest() function to determine if the click was on the checkbox.
void CTreeCtrlX::OnLButtonDown(UINT nFlags, CPoint point) 
{
	UINT uFlags=0;
	HTREEITEM hItem = HitTest(point,&uFlags);

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

Step 7: Add code to OnKeyDown to toggle checkbox

This step provides the keyboard interface for checking and unchecking an item. The key used is usually the space key, so we will use the space key. The code is nearly the same as in the previous step. Instead of using HitTest() to determine the item handle, we use the function GetSelectedItem().
void CTreeCtrlX::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if( nChar == VK_SPACE )
	{
		HTREEITEM hItem = GetSelectedItem();
		SetCheck( hItem, TOGGLE );
		return;
	}
}

Step 8: Define helper functions

Define helper functions to determine whether an item is checked or not and to iterate through the checked items. The purpose of the functions is very clear from the function names. The GetFirstCheckedItem() and the GetNextCheckedItem() are optimized. They look at the item state before deciding whether it should search the children items. The GetPrevCheckedItem() function searches sequentially through all items. I leave it as an exercise to optimize this function.
BOOL CTreeCtrlX::IsItemChecked(HTREEITEM hItem)
{
	int iImage = GetItemState( hItem, TVIS_STATEIMAGEMASK )>>12;
	return iImage == CHECKED || iImage == SELF_AND_CHILD_CHECKED;
}

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

	return NULL;
}

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

	return NULL;
}

HTREEITEM CTreeCtrlX::GetPrevCheckedItem( HTREEITEM hItem )
{
	for ( hItem = GetPrevItem( hItem ); hItem!=NULL; hItem = GetPrevItem( hItem ) )
		if ( IsItemChecked(hItem) )
			return hItem;

	return NULL;
}


The GetNextItem() function with single argument and the GetPrevItem() function have been defined earlier in the Get next item in outline and the Get previous item in outline topics.



Comments

  • good arts

    Posted by panqianliang on 04/08/2010 02:33am

    thank !

    Reply
  • thanks!

    Posted by jiqiubo on 03/18/2008 04:00am

    good control!Thank you!

    Reply
  • goog

    Posted by KANLEE2006 on 09/30/2006 12:36am

    goog

    Reply
  • --

    Posted by Gerom on 03/04/2005 06:30am

    Hey can you send me demo project I need it 
    my mail geronimo2005@bk.ru

    Reply
  • Thanks! but I need Demo Project! Send me a project. Please

    Posted by Legacy on 12/30/2003 12:00am

    Originally posted by: Richard

    Your Control is very useful.
    But I need Demo Project.
    Please, Send me a Sample Project.
    I have no time. Thanks!

    Reply
  • IS it possible to add check to for each tree items instead of all.

    Posted by Legacy on 11/18/2003 12:00am

    Originally posted by: Shiv Kumar

    This article shows how to add check box to each tree item. IS it possible to add check to for each tree items instead of all.If yes please let me know.
    Thanks
    Shiv

    Reply
  • No Sound please

    Posted by Legacy on 08/08/2003 12:00am

    Originally posted by: Manuel

    Hi,

    there always accures a sound by activating a checkbox via SPACE.
    how can i change this?

    thx
    Manuel

    Reply
  • don't work

    Posted by Legacy on 06/30/2003 12:00am

    Originally posted by: Ed

    i can't get this to work.

    my checkboxes only show 2 states checked and unchecked.
    it wont display the other bitmaps
    can somebody help


    thanks

    Reply
  • Give it a go

    Posted by Legacy on 04/23/2003 12:00am

    Originally posted by: ugly

    By looking that many of you request the source code and yet it is (the source code) not posted, we should realize that this guy is not giving out one. i just feel sorry for your guy :-) he does not even reply or give comment to any the request. So why bother. I dont see it is that hard to implement that feature. Give it a go and you may learn more than what he says.

    Reply
  • Adding Advanced Checkbox

    Posted by Legacy on 03/17/2003 12:00am

    Originally posted by: Don

    I need the demo. Thanks

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds