iczelion tut19
Tutorial 19: Tree View Control
In this tutorial, we will learn how to use tree view control. Moreover, we will also learn how to do drag and drop under tree view control and how to use an image list with it.
Download the example here.
Theory:
A tree view control is a special kind of window that represents objects in hierarchical order. An example of it is the left pane of Windows Explorer. You can use this control to show relationships between objects.You can create a tree view control by calling CreateWindowEx, passing "SysTreeView32" as the class name or you can incorporate it into a dialog box. Don't forget to put InitCommonControls call in your code.
There are several styles specific to the tree view control. These three are the ones mostly used.
- TVS_HASBUTTONS == Displays plus (+) and minus (-) buttons next to parent items. The user clicks the buttons to expand or collapse a parent item's list of child items. To include buttons with items at the root of the tree view, TVS_LINESATROOT must also be specified. 
 
TVS_HASLINES == Uses lines to show the hierarchy of items.
TVS_LINESATROOT == Uses lines to link items at the root of the tree-view control. This value is ignored if TVS_HASLINES is not also specified.
When something interesting occurs to it, it sends a WM_NOTIFY message to the parent window with accompanying information.
- WM_NOTIFY 
 
wParam == Control ID, this value is not guaranteed to be unique so we don't use it.
Instead, we use hwndFrom or IDFrom member of the NMHDR structure
pointed to by lParam
lParam == Pointer to NMHDR structure. Some controls may pass a pointer to larger
structure but it must have a NMHDR structure as its first member.
That is, when you have lParam, you can be sure that it points to a
NMHDR structure at least.
- NMHDR struct DWORD 
 
hwndFrom DWORD ?
idFrom DWORD ?
code DWORD ?
NMHDR ends
idFrom is the control ID of the control that sends this WM_NOTIFY message.
code is the actual message the control wants to send to the parent window.
Tree view notifications are those with TVN_ at the beginning of the name. Tree view messages are those with TVM_, like TVM_CREATEDRAGIMAGE. The tree view control sends TVN_xxxx in the code member of NMHDR. The parent window can send TVM_xxxx to control it.
Adding items to a tree view control
After you create a tree view control, you can add items to it. You can do this by sending TVM_INSERTITEM to it.- TVM_INSERTITEM 
 
wParam = 0;
lParam = pointer to a TV_INSERTSTRUCT;
An item can be parent, child, or both at the same time. A parent item is the item that has some other subitem(s) associated with it. At the same time, the parent item may be a child of some other item. An item without a parent is called a root item. There can be many root items in a tree view control. Now we examine TV_INSERTSTRUCT structure
- TV_INSERTSTRUCT STRUCT DWORD 
 
hParent DWORD ?
hInsertAfter DWORD ?
ITEMTYPE <>
TV_INSERTSTRUCT ENDS
hInsertAfter = Handle to the item after which the new item is to be inserted or one of the following values:
- TVI_FIRST ==> Inserts the item at the beginning of the list.
 - TVI_LAST ==> Inserts the item at the end of the list.
 - TVI_SORT ==> Inserts the item into the list in alphabetical order.
 
- ITEMTYPE UNION 
 
itemex TVITEMEX <>
item TVITEM <>
ITEMTYPE ENDS
- TV_ITEM STRUCT DWORD 
 
imask DWORD ?
hItem DWORD ?
state DWORD ?
stateMask DWORD ?
pszText DWORD ?
cchTextMax DWORD ?
iImage DWORD ?
iSelectedImage DWORD ?
cChildren DWORD ?
lParam DWORD ?
TV_ITEM ENDS
imask is used to specify which member(s) of the TV_ITEM structure is (are) valid. For example, if the value in imask is TVIF_TEXT, it means only the pszText member is valid. You can combine several flags together.
hItem is the handle to the tree view item. Each item has its own handle, like a window handle. If you want to do something with an item, you must select it by its handle.
pszText is the pointer to a null-terminated string that is the label of the tree view item.
cchTextMax is used only when you want to retrieve the label of the tree view item. Because you will supply the pointer to the buffer in pszText, Windows has to know the size of the provided buffer. You have to give the size of the buffer in this member.
iImage and iSelectedImage refers to the index into an image list that contains the images to be shown when the item is not selected and when it's selected. If you recall Windows Explorer left pane, the folder images are specified by these two members.
In order to insert an item into the tree view control, you must at least fill in the hParent, hInsertAfter and you should fill imask and pszText members as well.
Adding images to the tree view control
If you want to put an image to the left of the tree view item's label, you have to create an image list and associate it with the tree view control. You can create an image list by calling ImageList_Create.- ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD,  
 
cInitial:DWORD, cGrow:DWORD
cx == width of each image in this image list, in pixels.
cy == height of each image in this image list, in pixels. Every image in an image list must be equal to each other in size. If you specify a large bitmap, Windows will use cx and cy to *cut* it into several pieces. So you should prepare your own image as a strip of pictures with identical dimensions.
flags == specify the type of images in this image list whether they are color or monochrome and their color depth. Consult your win32 api reference for more detail
cInitial == The number of images that this image list will initially contain. Windows will use this info to allocate memory for the images.
cGrow == Amount of images by which the image list can grow when the system needs to resize the list to make room for new images. This parameter represents the number of new images that the resized image list can contain.
An image list is not a window! It's only an image deposit for use by other windows.
After an image list is created, you can add images to it by calling ImageList_Add
- ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD 
 
himl == the handle of the image list you want to add images to. It is the value returned by a successful call to ImageList_Create
hbmImage == the handle to the bitmap to be added to the image list. You usually store the bitmap in the resource and load it with LoadBitmap call. Note that you don't have to specify the number of images contained in this bitmap because this information is inferred from cx and cy parameters passed to ImageList_Create call.
hbmMask == Handle to the bitmap that contains the mask. If no mask is used with the image list, this parameter is ignored.
Normally, we will add only two images to the image list for use with the tree view control: one that is used when the tree view item is not selected, and the other when the item is selected.
When the image list is ready, you associate it with the tree view control by sending TVM_SETIMAGELIST to the tree view control.
- TVM_SETIMAGELIST 
 - TVSIL_NORMAL Set the normal image list, which contains the selected and unselected images for the tree-view item.
 - TVSIL_STATE Set the state image list, which contains the images for tree-view items that are in a user-defined state.
 
wParam = type of image list to set. There are two choices:
Retrieve the info about tree view item
You can retrieve the information about a tree view item by sending TVM_GETITEM message.- TVM_GETITEM 
 
wParam = 0
lParam = pointer to the TV_ITEM structure to be filled with the information
The answer is quite simple: you don't have to. You can send TVM_GETNEXTITEM message to the tree view control to retrieve the handle to the tree view item that has the attribute(s) you specified. For example, you can query the handle of the first child item, the root item, the selected item, and so on.
- TVM_GETNEXTITEM 
 
wParam = flag
lParam = handle to a tree view item (only necessary for some flag values)
- TVGN_CARET Retrieves the currently selected item.
 - TVGN_CHILD Retrieves the first child item of the item specified by the hitem parameter
 - TVGN_DROPHILITE Retrieves the item that is the target of a drag-and-drop operation.
 - TVGN_FIRSTVISIBLE Retrieves the first visible item.
 - TVGN_NEXT Retrieves the next sibling item.
 - TVGN_NEXTVISIBLE Retrieves the next visible item that follows the specified item. The specified item must be visible. Use the TVM_GETITEMRECT message to determine whether an item is visible.
 - TVGN_PARENT Retrieves the parent of the specified item.
 - TVGN_PREVIOUS Retrieves the previous sibling item.
 - TVGN_PREVIOUSVISIBLE Retrieves the first visible item that precedes the specified item. The specified item must be visible. Use the TVM_GETITEMRECT message to determine whether an item is visible.
 - TVGN_ROOT Retrieves the topmost or very first item of the tree-view control.
 
Drag and Drop Operation in tree view control
This part is the reason I decided to write this tutorial. When I tried to follow the example in win32 api reference (the win32.hlp from InPrise), I was very frustrated because the vital information is lacking. From trial and error, I finally figured out how to implement drag & drop in a tree view control and I don't want anyone to walk the same path as myself.Below is the steps in implementing drag & drop operation in a tree view control.
- When the user tries to drag an item, the tree view control sends TVN_BEGINDRAG notification to the parent window. You can use this opportunity to create a drag image which is the image that will be used to represent the item while it's being dragged. You can send TVM_CREATEDRAGIMAGE to the tree view control to tell it to create a default drag image from the image that is currently used by the item that will be dragged. The tree view control will create an image list with just one drag image and return the handle to that image list to you.
 - After the drag image is created, you specify the hotspot of the drag image by calling ImageList_BeginDrag. 
- ImageList_BeginDrag PROTO himlTrack:DWORD,   
 
iTrack:DWORD ,
dxHotspot:DWORD,
dyHotspot:DWORD
himlTrack is the handle to the image list that contains the drag image.
iTrack is the index into the image list that specifies the drag image
dxHotspot specifies the relative distance of the hotspot in horizontal plance in the drag image since this image will be used in place of the mouse cursor, so we need to specify which part of the image is the hotspot.
dyHotspot specifies the relative distance of the hotspot in the vertical plane.
Normally, iTrack would be 0 if you tell the tree view control to create the drag image for you. and dxHotspot and dyHotspot can be 0 if you want the left upper corner of the drag image to be the hotspot. - When the drag image is ready to be displayed, we call ImageList_DragEnter to display the drag image in the window. 
- ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD 
 
hwndLock is the handle of the window that owns the drag image. The drag image will not be able to move outside that window.
x and y are the x-and y-coordinate of the place where the drag image should be initially displayed. Note that these values are relative to the left upper corner of the window, not the client area. - Now that the drag image is displayed on the window, you will have to support the drag operation in the tree view control. However, there is a little problem here. We have to monitor the drag path with WM_MOUSEMOVE and the drop location with WM_LBUTTONUP messages. However, if the drag image is over some other child windows, the parent window will never receive any mouse message. The solution is to capture the mouse input with SetCapture. Using the call, the mouse messages will be directed to the specified window regardless of where the mouse cursor is.
 - Within WM_MOUSEMOVE handler, you will update the drag path with ImageList_DragMove call. This function moves the image that is being dragged during a drag-and-drop operation. Furthermore, if you so desire, you can hilite the item that the drag image is over by sending TVM_HITTEST to check if the drag image is over some item. If it is, you can send TVM_SELECTITEM with TVGN_DROPHILITE flag to hilite that item. Note that before sending TVM_SELECTITEM message, you must hide the drag image first else your drag image will leave ugly traces. You can hide the drag image by calling ImageList_DragShowNolock and, after the hilite operation is finished, call ImageList_DragShowNolock again to show the drag image.
 - When the user releases the left mouse button, you must do several things. If you hilite an item, you must un-hilite it by sending TVM_SELECTITEM with TVGN_DROPHILITE flag again, but this time, lParam MUST be zero. If you don't un-hilite the item, you will get a strange effect: when you select some other item, that item will be enclosed by a rectangle but the hilite will still be on the last hilited item. Next, you must call ImageList_DragLeave followed by ImageList_EndDrag. You must release the mouse by calling ReleaseCapture. If you create an image list, you must destroy it by calling ImageList_Destroy. After that, you can go on with what your program wants to do when the drag & drop operation is completed.
 
Code sample:
.386.model flat,stdcall
option casemap:none
include masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
include masm32includecomctl32.inc
include masm32includegdi32.inc
includelib masm32libgdi32.lib
includelib masm32libcomctl32.lib
includelib masm32libuser32.lib
includelib masm32libkernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD 
.const 
IDB_TREE equ 4006                ; ID of the bitmap resource 
.data 
ClassName  db "TreeViewWinClass",0 
AppName    db "Tree View Demo",0 
TreeViewClass  db "SysTreeView32",0 
Parent  db "Parent Item",0 
Child1  db "child1",0 
Child2  db "child2",0 
DragMode  dd FALSE                ; a flag to determine if we are in drag mode 
.data? 
hInstance  HINSTANCE ? 
hwndTreeView dd ?            ; handle of the tree view control 
hParent  dd ?                        ; handle of the root tree view item 
hImageList dd ?                    ; handle of the image list used in the tree view control 
hDragImageList  dd ?        ; handle of the image list used to store the drag image 
.code 
start: 
    invoke GetModuleHandle, NULL 
    mov    hInstance,eax 
    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT 
    invoke ExitProcess,eax 
    invoke InitCommonControls 
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 
    LOCAL wc:WNDCLASSEX 
    LOCAL msg:MSG 
    LOCAL hwnd:HWND 
    mov   wc.cbSize,SIZEOF WNDCLASSEX 
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra,NULL 
    mov   wc.cbWndExtra,NULL 
    push  hInst 
    pop   wc.hInstance 
    mov   wc.hbrBackground,COLOR_APPWORKSPACE 
    mov   wc.lpszMenuName,NULL 
    mov   wc.lpszClassName,OFFSET ClassName 
    invoke LoadIcon,NULL,IDI_APPLICATION 
    mov   wc.hIcon,eax 
    mov   wc.hIconSm,eax 
    invoke LoadCursor,NULL,IDC_ARROW 
    mov   wc.hCursor,eax 
    invoke RegisterClassEx, addr wc 
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,           WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT, 
           CW_USEDEFAULT,200,400,NULL,NULL, 
           hInst,NULL 
    mov   hwnd,eax 
    .while TRUE 
        invoke GetMessage, ADDR msg,NULL,0,0 
        .BREAK .IF (!eax) 
        invoke TranslateMessage, ADDR msg 
        invoke DispatchMessage, ADDR msg 
    .endw 
    mov eax,msg.wParam 
    ret 
WinMain endp 
WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
    LOCAL tvinsert:TV_INSERTSTRUCT 
    LOCAL hBitmap:DWORD 
    LOCAL tvhit:TV_HITTESTINFO 
    .if uMsg==WM_CREATE 
        invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL, 
            WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0, 
            0,200,400,hWnd,NULL, 
            hInstance,NULL            ; Create the tree view control 
        mov hwndTreeView,eax 
        invoke ImageList_Create,16,16,ILC_COLOR16,2,10    ; create the associated image list 
        mov hImageList,eax 
        invoke LoadBitmap,hInstance,IDB_TREE        ; load the bitmap from the resource 
        mov hBitmap,eax 
        invoke ImageList_Add,hImageList,hBitmap,NULL    ; Add the bitmap into the image list 
        invoke DeleteObject,hBitmap    ; always delete the bitmap resource 
        invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList 
        mov tvinsert.hParent,NULL 
        mov tvinsert.hInsertAfter,TVI_ROOT 
        mov tvinsert.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE 
        mov tvinsert.item.pszText,offset Parent 
        mov tvinsert.item.iImage,0 
        mov tvinsert.item.iSelectedImage,1 
        invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert 
        mov hParent,eax 
        mov tvinsert.hParent,eax 
        mov tvinsert.hInsertAfter,TVI_LAST 
        mov tvinsert.item.pszText,offset Child1 
        invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert 
        mov tvinsert.item.pszText,offset Child2 
        invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert 
    .elseif uMsg==WM_MOUSEMOVE 
        .if DragMode==TRUE 
            mov eax,lParam 
            and eax,0ffffh 
            mov ecx,lParam 
            shr ecx,16 
            mov tvhit.pt.x,eax 
            mov tvhit.pt.y,ecx 
            invoke ImageList_DragMove,eax,ecx 
            invoke ImageList_DragShowNolock,FALSE 
            invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit 
            .if eax!=NULL 
                invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax 
            .endif 
            invoke ImageList_DragShowNolock,TRUE 
        .endif 
    .elseif uMsg==WM_LBUTTONUP 
        .if DragMode==TRUE 
            invoke ImageList_DragLeave,hwndTreeView 
            invoke ImageList_EndDrag 
            invoke ImageList_Destroy,hDragImageList 
            invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0 
            invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax 
            invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0 
            invoke ReleaseCapture 
            mov DragMode,FALSE 
        .endif 
    .elseif uMsg==WM_NOTIFY 
        mov edi,lParam 
        assume edi:ptr NM_TREEVIEW 
        .if [edi].hdr.code==TVN_BEGINDRAG 
            invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem 
            mov hDragImageList,eax 
            invoke ImageList_BeginDrag,hDragImageList,0,0,0 
            invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y 
            invoke SetCapture,hWnd 
            mov DragMode,TRUE 
        .endif 
        assume edi:nothing 
    .elseif uMsg==WM_DESTROY 
        invoke PostQuitMessage,NULL 
    .else 
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam 
        ret 
    .endif 
    xor eax,eax 
    ret 
WndProc endp 
end start 
Analysis:
Within WM_CREATE handler, you create the tree view control-         invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL, 
 
WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,
0,200,400,hWnd,NULL,
hInstance,NULL
-         invoke ImageList_Create,16,16,ILC_COLOR16,2,10 
 
mov hImageList,eax
invoke LoadBitmap,hInstance,IDB_TREE
mov hBitmap,eax
invoke ImageList_Add,hImageList,hBitmap,NULL
invoke DeleteObject,hBitmap
invoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList
-         mov tvinsert.hParent,NULL 
 
mov tvinsert.hInsertAfter,TVI_ROOT
mov tvinsert.u.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE
mov tvinsert.u.item.pszText,offset Parent
mov tvinsert.u.item.iImage,0
mov tvinsert.u.item.iSelectedImage,1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
-         mov hParent,eax 
 
mov tvinsert.hParent,eax
mov tvinsert.hInsertAfter,TVI_LAST
mov tvinsert.u.item.pszText,offset Child1
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
mov tvinsert.u.item.pszText,offset Child2
invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert
-     .elseif uMsg==WM_NOTIFY 
 
mov edi,lParam
assume edi:ptr NM_TREEVIEW
.if [edi].hdr.code==TVN_BEGINDRAG
invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItem
mov hDragImageList,eax
invoke ImageList_BeginDrag,hDragImageList,0,0,0
invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.y
invoke SetCapture,hWnd
mov DragMode,TRUE
.endif
assume edi:nothing
-    .elseif uMsg==WM_MOUSEMOVE 
 
.if DragMode==TRUE
mov eax,lParam
and eax,0ffffh
mov ecx,lParam
shr ecx,16
mov tvhit.pt.x,eax
mov tvhit.pt.y,ecx
invoke ImageList_DragMove,eax,ecx
invoke ImageList_DragShowNolock,FALSE
invoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit
.if eax!=NULL
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax
.endif
invoke ImageList_DragShowNolock,TRUE
.endif
-     .elseif uMsg==WM_LBUTTONUP 
 
.if DragMode==TRUE
invoke ImageList_DragLeave,hwndTreeView
invoke ImageList_EndDrag
invoke ImageList_Destroy,hDragImageList
invoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eax
invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0
invoke ReleaseCapture
mov DragMode,FALSE
.endif
