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, an of understanding linked lists, pointers, and not very common memory allocation techniques is required as well.

TreeList.gif

Now, having said that, we can safely continue and focus on the following aspects.

Using the code

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 comes in as WM_NOTIFY message. 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 LPNMTVCUSTOMDRAW 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;      // 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 the first sibling
    struct tag_TreeListNode     *pBrother;          // Node pointer to the first 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.

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 : // This is the place to start the control
        {
            // Control setup..
            TreeListInitControl(GetModuleHandle(NULL), hWndDlg, NULL, 
              &Container_ValidateEditRequest);

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.

TreeListInitControl (HINSTANCE Instance, HWND ParentHwnd, RECT *pRect, TREELIST_CB *pFunc);

Init the control. I guess that if you are here, the Instance and the parent window handler parameters are obvious. pRect is the absolute position of the control on top of its parent window, and unfortunately for you, it's not implemented (yet).. sorry. That last parameter is a callback pointer to your edit request handler. It will be invoked whenever the caller wishes to edit a cell.

TreeListAddColumn(char *szColumnName,int Width);

Create the columns, make sure you set Width = TREELIST_LAST_COLUMN in your last column width param.

TreeListNodeAdd(NODE_HANDLE ParentHandle,TreeListNodeData *RowOfColumns,int ColumnsCount);

Add a node. Here you must specify the parent node handler (set NULL for the root node), and an array of TreeListNodeData structs that represent each column. Lastly, you must provide the elements count within this array. Following is a short description of the TreeListNodeData struct. The call returns a handle to the node that was created, you can use this node to add the node's siblings.

static 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    Altered;        // Internal
    long    CRC;            // Internal
   
};
typedef struct tag_TreeListNodeData TreeListNodeData;

The last thing to do is route the window messages to the control so we can handle them within the internal window procedure.

        case WM_CLOSE:
        {

            TreeListDestroy(); // Kill and free the control info
            EndDialog(hWndDlg, IDOK);
            return TRUE;;
        }
    }
    return TreeListHandleMessages(hWndDlg, Msg, wParam, lParam);
    // TreeList Control handler
}

History

  • Version 1.2: This is the initial version of this control.
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架