Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Win32 Editable TreeView and ListView Merged as One

4.93/5 (59 votes)
26 Apr 2011CPOL6 min read 148.9K   12K  
A custom tree control for Win32.

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

Image 1

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.

C++
TREELIST_HANDLE TreeListCreate (
   HINSTANCE Instance,    // Application instance
   HWND Hwnd,             // The parent window handler
   RECT *pRect,           // the controls size, NULL will set it to full client size
   DWORD dwFlags,         // See TreeList.h 'Control creation flags' section
   TREELIST_CB *pFunc);   // a call back to validate the user edit requests (can be NULL)

The return value is a valid handle to the control. Following is the edit request call back:

C++
typedef BOOL _stdcall TREELIST_CB(
   const TREELIST_HANDLE,  // The Handle to the control
   const void *pAnyPtr,    // Optional a user pointer that
                           // was added to the node (TreeListAddNode)
   const char *NewData,    // The data that is about to be added to the tree
   char *Override);        // Set this if you want to override
                           // the user request, and force something else

// If you return TRUE you accept the edit request, FALSE will reject it.

The next step is to create the columns; we will do this by calling:

C++
TreeListError TreeListAddColumn (
   TREELIST_HANDLE ListTreeHandle,  // a valid handle (created with TreeListCreate)
   char *szColumnName,              // a null terminated string
   int Width);                      // Column width in pixels

// Make sure to set TREELIST_LAST_COLUMN as the last column width parameter.

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.

C++
NODE_HANDLE TreeListAddNode (
   TREELIST_HANDLE ListTreeHandle,      // a valid handle (created with TreeListCreate)
   NODE_HANDLE ParentHandle,            // a handle to our parent node
                                        // (NULL for a the first root node)
   TreeListNodeData *RowOfColumns,      // an array of TreeListNodeData
                                        // structs (for each column)
   int ColumnsCount);                   // The count of elements in RowOfColumns

// The return value is a handle to a node that will
// be used to create the next (siblings) nodes

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.

C++
struct tag_TreeListNodeData
{
    char      Data    [TREELIST_MAX_STRING +1]; // The string to display
    BOOL      Editable;          // Is it an editable cell?
    BOOL      Numeric;           // Is it a numeric cell?
    void      *pExternalPtr;     // a caller pointer, will be sent back
                                 // along with the call back function.
    BOOL      Colored;           // Are we using colors?
    COLORREF  BackgroundColor;   // Cell background color
    COLORREF  TextColor;         // Text color
    COLORREF  AltertedTextColor; // Color for edited text
    BOOL      Altered;           // Internal - do not modify
    long      CRC;               // Internal - do not modify

};
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.).

C++
int TreeListDestroy (TREELIST_HANDLE ListTreeHandle);
// a valid handle (created with TreeListCreate)

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:

C++
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_PREPAINTBefore painting the entire ListView control.
CDDS_ITEMPREPAINTBefore painting an item within the tree.
CDDS_ITEMPOSTPAINTJust 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:

C++
static struct tag_TreeListNode
{
    int                         NodeDataCount;      // Count of items in pNodeData
    HTREEITEM                   TreeItemHandle;     // Handle to the tree item (windows)
    struct tag_TreeListNode     *pParennt;          // Node pointer to the parent
    struct tag_TreeListNode     *pSibling;          // Node pointer to a sibling
    struct tag_TreeListNode     *pBrother;          // Node pointer to a brother
    TreeListNodeData            **pNodeData;        // Array of NodeData structures
                                                    // for each column

};
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:

C++
INT_PTR CALLBACK WinWndProc(HWND hWndDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
        case WM_INITDIALOG : // This is the place to start the control
        {
            // Control setup..
            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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)