Introduction
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.
Key Features
- Multi-columns
- 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 (
HINSTANCE Instance, HWND Hwnd, RECT *pRect, DWORD dwFlags, TREELIST_CB *pFunc);
The return value is a valid handle to the control. Following is the edit request call back:
typedef BOOL _stdcall TREELIST_CB(
const TREELIST_HANDLE, const void *pAnyPtr, const char *NewData, char *Override);
The next step is to create the columns; we will do this by calling:
TreeListError TreeListAddColumn (
TREELIST_HANDLE ListTreeHandle, char *szColumnName, int Width);
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 (
TREELIST_HANDLE ListTreeHandle, NODE_HANDLE ParentHandle, TreeListNodeData *RowOfColumns, int ColumnsCount);
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.
struct tag_TreeListNodeData
{
char Data [TREELIST_MAX_STRING +1]; BOOL Editable; BOOL Numeric; void *pExternalPtr; BOOL Colored; COLORREF BackgroundColor; COLORREF TextColor; COLORREF AltertedTextColor; BOOL Altered; long CRC;
};
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:
case WM_NOTIFY:
{
lpNMHeader = (LPNMHDR)lParam;
switch(lpNMHeader->code)
{
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:
CDDS_PREPAINT | Before painting the entire ListView control. |
CDDS_ITEMPREPAINT | Before painting an item within the tree. |
CDDS_ITEMPOSTPAINT | 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 TreeView_GetItemRect()
, FillRect()
, DrawEdge()
, 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
{
int NodeDataCount; HTREEITEM TreeItemHandle; struct tag_TreeListNode *pParennt; struct tag_TreeListNode *pSibling; struct tag_TreeListNode *pBrother; TreeListNodeData **pNodeData;
};
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.
See TreeList.c\TreeList_Internal_CRCCreate()
and TreeList.c\TreeList_Internal_CRCCheck()
for more information.
Multiple instances
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 SetProp
and GetProp
; the tricky part is to set multiple instances and to get the correct one each time. Refer to the internal functions TreeList_Internal_DictGetPtr()
and 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)
{
switch (Msg)
{
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.
History
- 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.