.Net Framework

Win32 Editable TreeView and ListView Merged as One

프로필

2011. 4. 12. 13:06

이웃추가

[출처: http://www.codeproject.com/KB/tree/Win32TreeList.aspx]

 

 

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.

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

Using the codeDrawing 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:

Collapse | Copy Code
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:

Collapse | Copy Code
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:

Collapse | Copy Code
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.

Collapse | Copy Code
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.

Collapse | Copy Code
TreeListAddColumn(char *szColumnName,int Width);

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

Collapse | Copy Code
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.

Collapse | Copy Code
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.

Collapse | Copy Code
case WM_CLOSE: { TreeListDestroy(); // Kill and free the control info EndDialog(hWndDlg, IDOK); return TRUE;; } } return TreeListHandleMessages(hWndDlg, Msg, wParam, lParam); // TreeList Control handler }
찌니
찌니

별은 반딧불로 보이는 것을 두려워 하지 않는다