Introduction
I was in need of a tree control with several features that are not found in the standard CTreeCtrl
:
- Checkboxes with indeterminate state. When a tree item's child items are not all in the same state then that item's checkbox should show an indeterminate state.
- Setting a parent tree item's checkbox state to checked or unchecked should automatically set all its child tree item's states to the same state.
- Setting a child tree item's checkbox state to checked or unchecked should automatically set its parent tree item's state to the appropriate state.
- Not all the items in the tree need to have a checkboxes. Sometimes, the root of the tree just needs to be a labeled place holder.
- Notifications. It would be really nice if the parent window of the tree control recieved detailed notification messages when a tree item's checkbox state changes.
- Ability to block a change in a checkbox state. When a notification (see above) is received by the parent window, it should be possible to block the checkbox state from changing.
Presented in this article is the CQuadStateTree
class that delivers all these requirements, plus a few more.
Background
Why Quad State you may ask? It is because each checkbox in the tree control can have four states. Of course, there is the basic checked and unchecked, and the familiar indeterminate state. But I have added a forth state which is 'none
'. You may say that 'none
' is not a state, but it actually is. How else would one specify that a tree item not have a checkbox when the rest of the tree control does have them?
Of course to have the 'none
' state make any sense there has to be a basic restriction placed on it. The restriction being that in order for a tree item to have a 'none
' state, all of its parent items up the tree, all the way up to the root, must also be in the 'none
' state. It makes no sense to able to select a parent item and not be able to select a child item.
Using the Code
The first step for using this code in your own projects is to add the QuadStateTree.h and QuadStateTree.cpp files to your project. Then in any file that needs access to the CQuadStateTree
class, you just have to #include QuadStateTree.h.
Since CQuadStateTree
is derived from CTreeCtrl
, you can use this class the exact same way you would use CTreeCtrl
with the following API exceptions:
CQuadStateTree Data Types
The CQuadStateTree
uses the following custom data types.
The TVCS_CHECKSTATE
enum
defines the four possible checkbox states.
Syntax
enum TVCS_CHECKSTATE
{
TVCS_NONE = -1,
TVCS_UNCHECKED = 0,
TVCS_CHECKED = 1,
TVCS_INDETERMINATE = 2
};
Members
TVCS_NONE | The tree item has no checkbox associated with it. |
TVCS_UNCHECKED | The tree item's checkbox does not have a check mark in it. |
TVCS_CHECKED | The tree item's checkbox has a check mark in it. |
TVCS_INDETERMINATE | The tree item has child items, and the child items are a mix of checked and unchecked. |
The NMTVNCHECK
structure contains information about a change in a tree item's checkbox state.
Syntax
typedef struct tagTVNCHECK
{
NMHDR hdr;
HTREEITEM hTreeItem;
LPARAM lParam;
TVCS_CHECKSTATE OldCheckState;
TVCS_CHECKSTATE NewCheckState;
HTREEITEM TriggerItem;
} NMTVNCHECK, *LPNMTVNCHECK;
Members
hdr | NMHDR structure that contains information about the TVN_CHECK notification message. |
hTreeItem | Handle to the tree item whose checkbox is changing state. |
lParam | Application specific data that is associated with hTreeItem . |
OldCheckState | The old checkbox state for hTreeItem . |
NewCheckState | The new checkbox state for hTreeItem . |
TriggerItem | Handle to the tree item that was toggled by the user or was specified in a call to SetCheck() . |
CQuadStateTree Notification Messages
The TVN_CHECK
notification is sent to the parent window as a WM_NOTIFY message to inform the parent that a checkbox is about to change state.
Syntax
TVN_CHECK
pNMTvnCheck = (LPNMTVNCHECK) lParam
Parameters
pNMTvnCheck | a pointer to a NMTVNCHECK structure. |
Return Value
If the hTreeItem
and TriggerItem
members of the NMTVNCHECK
structure are pointing to the same tree item then returning a non-zero value will stop the tree item checkbox from changing.
In all other cases, the return value is ignored.
ON_TVN_CHECK Message map macro
Use the ON_TVN_CHECK
macro to define an TVN_CHECK
notification handler function.
Syntax
ON_TVN_CHECK(<nID>, <pFunction>)
Parameters
nID | Identification number for the tree control |
pFunction | Pointer to the member function to be called when the TVN_CHECK notification is sent |
Remarks
pFunction
must be declared with the following prototype:
afx_msg void memberFxn( NMHDR * pNMHDR, LRESULT * pResult );
where the italicized parameters are:
pNMHDR
A pointer to a NMHDR
structure which can be cast to a LPNMTVNCHECK structure pointer.
pResult
A pointer to the result code that you set before you return.
CQuadStateTree Member Functions
CQuadStateTree::CQuadStateTree
Class constructor that is called to construct a CQuadStateTree
object.
Syntax
CQuadStateTree();
CQuadStateTree::~CQuadStateTree
Class destructor that is called when a CQuadStateTree
object is deleted.
Syntax
virtual ~CQuadStateTree();
Syntax
TVCS_CHECKSTATE GetCheck(
HTREEITEM hTreeItem) const;
Parameter
hTreeItem | Handle to the tree item to retrieve the checkbox state from. |
Return Value
Returns a TVCS_CHECKSTATE enum
indicating the current state of the checkbox.
Remarks
This function is identical to CTreeCtrl::GetCheck. The only difference is that the return value is returned as a TVCS_CHECKSTATE enum
instead of a BOOL
.
Syntax
BOOL SetCheck (
HTREEITEM hTreeItem,
TVCS_CHECKSTATE NewCheckState = TVCS_CHECKED);
Parameters
hTreeItem | Handle to the tree item to receive the checkbox state change. |
NewCheckState | The new checkbox state. By default, the checkbox state is set to TVCS_CHECKED . |
Return Value
Nonzero if successful; otherwise zero.
Remarks
SetCheck()
will return zero for the following reasons:
NewCheckState
is TVCS_INDETERMINATE
. The TVCS_INDETERMINATE
state will only be set by the control when a tree item's children are in different states. It is not possible to set TVCS_INDETERMINATE
via SetCheck()
. NewCheckState
is TVCS_NONE
. The only way to set a checkbox to TVCS_NONE
state is if all hTreeItem
's parent tree items are already set to TVCS_NONE
. To set TVCS_NONE
state you have to start at the tree's root item and work your way down.
Call this function to create a control (a child window) and associate it with the CQuadStateTree
object.
Syntax
virtual BOOL Create(
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID);
Parameters
dwStyle | Specifies the tree view control's style. Apply window styles, described in CreateWindow, and any combination of tree view control styles as described in the Platform SDK. |
rect | A reference to a RECT structure describing the size and position of the window to be created, in client coordinates of pParentWnd . |
pParentWnd | A pointer to the window that is the control's parent. |
nID | The control's child-window ID. |
Call this function to create a control (a child window) and associate it with the CQuadStateTree
object.
Syntax
virtual BOOL CreateEx(
DWORD dwExStyle,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
UINT nID);
Parameters
dwExStyle | Specifies the extended style of the control being created. For a list of extended Windows styles, see the dwExStyle parameter for CreateWindowEx in the Platform SDK. |
dwStyle | Specifies the tree view control's style. Apply window styles, described in CreateWindow, and any combination of tree view control styles as described in the Platform SDK. |
rect | A reference to a RECT structure describing the size and position of the window to be created, in client coordinates of pParentWnd . |
pParentWnd | A pointer to the window that is the control's parent. |
nID | The control's child-window ID. |
Remarks
I have not explored the tree view extended styles, so I have no idea how they will influence this control.
Deriving from CQuadStateTree
CQuadStateTree
has to handle several different messages in order to work properly. If you derive your own control from CQuadStateTree
and you handle the same messages in derived class, be sure to call the CQuadStateTree
message handler functions from your message handler functions.
Listed here is the messages that CQuadStateTree
handles and the name of the functions that handle them:
NM_CLICK | CQuadStateTree::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult) |
NM_TVSTATEIMAGECHANGING | CQuadStateTree::OnNMTvStateImageChanging(NMHDR *pNMHDR, LRESULT *pResult) |
TVN_KEYDOWN | CQuadStateTree::OnTvnKeydown(NMHDR *pNMHDR, LRESULT *pResult) |
TVN_ITEMCHANGED | CQuadStateTree::OnTvnItemchanged(NMHDR *pNMHDR, LRESULT *pResult) |
TVM_SETITEM | CQuadStateTree::OnTvmSetitem(WPARAM wp, LPARAM lp) |
History
- November 28, 2014 - Initial release