You can check items in a treeview control by using either an overlay image or a couple of state images. Both the techniques are fairly simple, but using a state image allows us to show a checkbox and makes it obvious to the user.
In this section we allow an item to have only two states. Either it is checked or it is not. For a tree this is not always the best choice. We may also want imformation as to whether any of the child or descendant items are checked or not. We will cover that in the next topic.
Step 1: Create bitmap with checkbox images
Create a bitmap with an image of an unchecked checkbox and an image with a checked checkbox. Since we will be using this bitmap for the state image list, the first image will not be used. So the sequence of images in the bitmap is – blank image, empty checkbox and checked checkbox.
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 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. When we get the state image index to determine whether the item is checked or not, the GetItemState() function returns the value in the form used internally by the tree control. Shifting the value by 12 bit places to the right gives us the proper index value. We then toggle the state of the item.
void CTreeCtrlX::OnLButtonDown(UINT nFlags, CPoint point) { UINT uFlags=0; HTREEITEM hti = HitTest(point,&uFlags); if( uFlags & TVHT_ONITEMSTATEICON ) { int iImage = GetItemState( hti, TVIS_STATEIMAGEMASK )>>12; SetItemState( hti, INDEXTOSTATEIMAGEMASK(iImage == 1 ? 2 : 1), TVIS_STATEIMAGEMASK ); return; } }
Step 5: 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 hti = GetSelectedItem(); int iImage = GetItemState( hti, TVIS_STATEIMAGEMASK )>>12; SetItemState( hti, INDEXTOSTATEIMAGEMASK(iImage == 1 ? 2 : 1), TVIS_STATEIMAGEMASK ); return; } }
Step 6: 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.
BOOL CTreeCtrlX::IsItemChecked(HTREEITEM hItem) { return GetItemState( hItem, TVIS_STATEIMAGEMASK )>>12 == 2; } HTREEITEM CTreeCtrlX::GetFirstCheckedItem() { for ( HTREEITEM hItem = GetRootItem(); hItem!=NULL; hItem = GetNextItem( hItem ) ) if ( IsItemChecked(hItem) ) return hItem; return NULL; } HTREEITEM CTreeCtrlX::GetNextCheckedItem( HTREEITEM hItem ) { for ( hItem = GetNextItem( hItem ); hItem!=NULL; hItem = GetNextItem( hItem ) ) if ( IsItemChecked(hItem) ) return hItem; return NULL; } HTREEITEM CTreeCtrlX::GetPrevCheckedItem( HTREEITEM hItem ) { for ( hItem = GetPrevItem( hItem ); hItem!=NULL; hItem = GetPrevItem( hItem ) ) if ( IsItemChecked(hItem) ) return hItem; return NULL; }