There are times when there is a need for a combination of the TreeView control along with the ListView control. A control that can present data in an expanding tree along with grid lines and editable columns. Unfortunately, such a control is not part of the Win32 basic controls (including those found within comctl32.lib). This article will show how to extend the TreeView control to meet this need. The included source files hold a complete TreeList control source that can be easily used in many projects. The project was written entirely in C, and with direct calls to the Win32 API without the use of any runtime library support (such as MFC and others). The reason behind this is to be as fast and as independent as possible. Furthermore, it can be merged with existing projects written in C without special modifications to the code.
I assume previous knowledge in Win32 native APIs, an understating of how a window works, and the way custom owner drawn controls are created. Since we are dealing with presenting complex data types, understanding linked lists is required as well.
- Editable nodes
- Can set background and text color for each node
- Can set special text color for altered nodes
- Can set anchors so the control will auto resize along with its parent
- The API consists of only 4 calls
This snapshot demonstrates four instances of the control attached to a dialog window.
Note for previous version of the control
This version will probably be the last one. During its development, I've changed the interface a little, so please be sure to use the latest source. Should any critical bugs be spotted, I will update to reflect the fix, but the interface will remain the same.
Using the code
The control is coded in TreeList.c and documented in TreeList.h. The included sample file Container.c contains a simple and easy to understand implementation. The API is thread safe, which means there are no problematic static elements, and that you can create multiple instances of it. The very first step is to create a control instance. This will allocate the memory needed for its internal session and create the TreeView and the Header windows on which this control is built on. Note that this call will subclass your parent window and route all the messages to the control.
TREELIST_HANDLE TreeListCreate (
The return value is a valid handle to the control. Following is the edit request call back:
typedef BOOL _stdcall TREELIST_CB(
const void *pAnyPtr,
const char *NewData,
The next step is to create the columns; we will do this by calling:
TreeListError TreeListAddColumn (
Now we can build the tree. Note that it is impossible to add more columns after the first call to
TreeListAddNode is made. We will call
TreeListAddNode for each node we add.
NODE_HANDLE TreeListAddNode (
The following is the description of the node struct; all of the node properties such as the ability to edit the data, its color and so on, are set by filling this struct.
char Data [TREELIST_MAX_STRING +1];
typedef struct tag_TreeListNodeData TreeListNodeData;
The last call is
TreeListDestroy; calling it with a proper handle will free all the memory that was allocated for it, and destroy all of the window objects associated with it (Windows, brushes, ect.).
int TreeListDestroy (TREELIST_HANDLE ListTreeHandle);
Now, having explained the control's usage, we can safely continue and focus on the following aspects:
Drawing the grid lines on top of the TreeView control
After having created the TreeView with
CreateWindowEx, we have to handle some of its messages in our Windows procedure. Most of the interesting messages come in as
WM_NOTIFY messages. The first thing we have to do is to extract the message hidden within the
LPARAM parameter. For doing this, we will cast the
LPARAM as a
LPNMHDR pointer and examine its '
code' member, as shown below:
lpNMHeader = (LPNMHDR)lParam;
The next step is to respond to the
NM_CUSTOMDRAW message. By doing this, we can intervene in the control drawing process and extend it to our needs. Once again, we will cast the
LPARAM, this time to a
LPNMTVCUSTOMDRAW pointer, and examine its
nmcd.dwDrawStage member. There are several stages in the control's creation process that we need to handle:
|Before painting the entire ListView control.|
|Before painting an item within the tree.|
|Just after the item is drawn.|
In each stage, we will have to redirect Windows to the next one, the purpose of this is to be able to do some work in the
CDDS_ITEMPOSTPAINT stage. Finally, when we get to the point where a break point stoops at
CDDS_ITEMPOSTPAINT, we can add the horizontal and vertical grid lines. The
nmcd structure member provides us, among other things, the control's DC and a handle to the tree item currently being drawn. With a mix of calls such as
DrawText(), we will draw those lines and the label's text on each of our columns.
Please refer to TreeLis.c\
TreeListHandleMessages() for more information.
The internal data type
This control has to store internally a dynamic tree along with the correct relations between each and every node. This is done be using the following type:
static struct tag_TreeListNode
struct tag_TreeListNode *pParennt;
struct tag_TreeListNode *pSibling;
struct tag_TreeListNode *pBrother;
typedef struct tag_TreeListNode TreeListNode;
Each time we're adding a new node, we are allocating memory for this structure and tying it to its surrounding nodes (parent and possibly a sibling). Each node represents an element in the tree, but since we have columns, it holds the
**pNodeData pointer which is in turn being allocated to hold an array of the columns attached to our node.
Please refer to TreeList.c\
TreeList_Internal_NodeAdd() for more information.
Data validity check
Since we are heavily working with pointers and dynamically allocated memory, I have added a safe guard to the data type. Each time a node is linked with another node, I'm verifying its data integrity using CRC. Each time a node is created or modified, its CRC value is calculated and attached to it.
TreeList_Internal_CRCCreate() and TreeList.c\
TreeList_Internal_CRCCheck() for more information.
To be able to create multiple instances of this control, I hade to store the session pointer and somehow access it within the
WNDPROC function. This could be easily achieved by attaching the pointer to the parent window and getting it back using
GetProp; the tricky part is to set multiple instances and to get the correct one each time. Refer to the internal functions
TreeList_Internal_DictUpdate() for more information.
The sample code (Container.c) and API usage
This sample file creates a dialog window and positions the control on top of it by attaching it to its
WM_INITDIALOG message in its Window procedure, as shown below:
INT_PTR CALLBACK WinWndProc(HWND hWndDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
case WM_INITDIALOG :
TreeListHandle = TreeListCreate(GetModuleHandle(NULL) ,hWndDlg,...);
If you are creating the host window using
CreateWidow(), you can put the TreeList calls in the
WM_CREATE message. Don't forget to free the control's memory by calling
TreeListDestroy() while exiting the host window.
- Version 1.2: This is the initial version of this control.
- Version 1.3: Added: Colors and auto resize; Fixed: mostly minor issues.
- Version 1.4: Added: The control now accepts a fixed
RECT and anchor to its parent.
- Version 1.5: Added: The API is thread safe and can create multiple instances of the control.
- Version 1.7: Bug fixes, interface change, creation flags.