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.

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. The tree view control, like other common controls, communicates with the parent window via messages. The parent window can send various messages to it and the tree view control can send "notification" messages to its parent window. In this regard, the tree view control is not different that any window.
When something interesting occurs to it, it sends a WM_NOTIFY message to the parent window with accompanying information. Next we will examine NMHDR structure. hwndFrom is the window handle of the control that sends this WM_NOTIFY message.
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. You should know some terminology at this point about the relationship between items in the tree view control.
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 hParent = Handle to the parent item. If this member is the TVI_ROOT value or NULL, the item is inserted at the root of the tree-view control.
hInsertAfter = Handle to the item after which the new item is to be inserted or one of the following values: We will use only TVITEM here. This structure is used to send and receive info about a tree view item, depending on messages. For example, with TVM_INSERTITEM, it is used to specify the attribute of the item to be inserted into the tree view control. With TVM_GETITEM, it'll be filled with information about the selected tree view item.
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. This function returns the handle to an empty image list if successful.
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 This function returns -1 if unsuccessful.
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.

Retrieve the info about tree view item

You can retrieve the information about a tree view item by sending TVM_GETITEM message. Before you send this message, you must fill imask member with the flag(s) that specifies which member(s) of TV_ITEM you want Windows to fill. And most importantly, you must fill hItem with the handle to the item you want to get information from. And this poses a problem: How can you know the handle of the item you want to retrieve info from? Will you have to store all tree view handles?
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. The value in wParam is very important so I present all the flags below: You can see that, you can retrieve the handle to the tree view item you are interested in from this message. SendMessage returns the handle to the tree view item if successful. You can then fill the returned handle into hItem member of TV_ITEM to be used with TVM_GETITEM message.

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.
  1. 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.
  2. After the drag image is created, you specify the hotspot of the drag image by calling ImageList_BeginDrag.
    1. 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.
  3. When the drag image is ready to be displayed, we call ImageList_DragEnter to display the drag image in the window.
    1. 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.
  4. 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.
  5. 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.
  6. 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:

___________________________________________________________________________________________

[IDB_TREE 4006]
 

[WindowHandle: 0  hwndTreeView: 0  hParent: 0  hBitmap: 0  hImageList: 0  hDragImageList: 0
 DragMode: B§ &FALSE
 WindowClassName: 'TreeViewWinClass' 0
 WindowCaption:   'Tree View Demo' 0
 TreeViewClass:   'SysTreeView32' 0
 Parent:          'Parent Item' 0
 Child1:          'child1' 0
 Child2:          'child2' 0]

[WindowClass:
 style: 3  lpfnWndProc: MainWindowProc   cbClsExtra: 0  cbWndExtra: 0
 hInstance: 0  hIcon: 0  hCursor: 0  hbrBackground: 6
 lpszMenuName: 0  lpszClassName: WindowClassName]

[FirstMessage: 0 #7]

____________________________________________________________________________________________
____________________________________________________________________________________________

Main:

    call 'Kernel32.GetModuleHandleA' 0 | mov D§hInstance eax
 
    call 'User32.LoadIconA'  0  &IDI_APPLICATION | mov D§hIcon eax
 
    call 'User32.LoadCursorA' 0  &IDC_ARROW | mov D§hCursor eax

    call 'User32.RegisterClassA'  WindowClass
 
    call 'User32.CreateWindowExA' &WS_EX_CLIENTEDGE WindowClassName WindowCaption,
          &WS_CAPTION+&WS_SYSMENU+&WS_MINIMIZEBOX+&WS_MAXIMIZEBOX+&WS_VISIBLE,
                                 &CW_USEDEFAULT  &CW_USEDEFAULT  200  400  &NULL,
                                 &NULL  D§hInstance  &NULL
        mov D§WindowHandle eax
 
    call 'User32.ShowWindow'  D§WindowHandle &SW_SHOW
    call 'User32.UpdateWindow'  D§WindowHandle

 
L1: call 'User32.GetMessageA' FirstMessage 0 0 0 | cmp eax 0 | je L9>
        call 'User32.TranslateMessage'  FirstMessage
        call 'User32.DispatchMessageA'  FirstMessage
    jmp L1<

L9: call 'Kernel32.ExitProcess' 0

_________________________________________________________________________________________

[TV_INSERTSTRUCT:  TVI_hParent: 0  TVI_hInsertAfter: &TVI_ROOT
 TVI_item: TVI_imask: &TVIF_TEXT+&TVIF_IMAGE+&TVIF_SELECTEDIMAGE
           TVI_hItem: 0       TVI_state: 0   TVI_stateMask: 0
           TVI_pszText: Parent    TVI_cchTextMax: 0  TVI_iImage: 0  TVI_iSelectedImage: 1
           TVI_cChildren: 0  TVI_lParam: 0]

[TVHITTESTINFO:
    TVHIT_POINTx: 0  TVHIT_POINTy: 0
    TVHIT_flags: 0
    TVHIT_hItem: 0]

; these structures are images of win internal ones. We use them just to set the pointers
; to win's one, sent as pointer in lParam with TVN_BEGINDRAG message, just in order to
; optain displacements inside these damned hidden structures. I do it here with direct
; data values written in the sets. A cleaner way, more consistant for readibility, is to
; adjust the pointers in the code, for exemple, instead of:
;
; > mov edi D§lParam, eax D§NMHDR_code
; > ..if D§edi+eax = TVN_BEGINDRAG
;
; we can do:
;
; > mov edi D§lParam | sub edi NM_TREEVIEW
; > ..if D§edi+NMHDR_code = TVN_BEGINDRAG
;
; I do it this second way in the Debug exemples.

[NM_TREEVIEW:  NMHDR_hwndFrom: 0
               NMHDR_idfrom: 0
               NMHDR_code: NMHDR_code - NM_TREEVIEW
               NM_action: 0
 
               NM_itemOld:
                  NMIO_mask: 0
                  NMIO_hItem: NMIN_hItem - NM_TREEVIEW
                  NMIO_state: 0      NMIO_stateMask: 0
                  NMIO_pszText: 0    NMIO_cchTextMax: 0
                  NMIO_iImage: 0     NMIO_iSelectedImage: 0
                  NMIO_Children: 0   NMIO_lParam: 0
 
               NM_itemNew:
                  NMIN_mask: 0
                  NMIN_hItem: NMIN_hItem - NM_TREEVIEW
                  NMIN_state: 0      NMIN_stateMask: 0
                  NMIN_pszText: 0    NMIN_cchTextMax: 0
                  NMIN_iImage: 0     NMIN_iSelectedImage: 0
                  NMIN_Children: 0   NMIN_lParam: 0
 
               NM_ptDragX: NM_ptDragX - NM_TREEVIEW
               NM_ptDragY: NM_ptDragY - NM_TREEVIEW]
 
[TV_ITEM: TV_ITEMmask: 0
          TV_ITEMhItem: TV_ITEMhItem - TV_ITEM
          TV_ITEMstate: 0      TV_ITEMstateMask: 0
          TV_ITEMpszText: 0    TV_ITEMcchTextMax: 0
          TV_ITEMiImage: 0     TV_ITEMiSelectedImage: 0
          TV_ITEMcChildren: 0  TV_ITEMlParam:  TV_ITEMlen: len]

___________________________________________________________________________________________

Proc MainWindowProc:
    Arguments @Adressee, @Message, @wParam, @lParam
 
    pushad

    .If D@Message = &WM_CREATE
        call 'USER32.CreateWindowExA' &NULL TreeViewClass  &NULL,
                        &WS_CHILD+&WS_VISIBLE+&TVS_HASLINES+&TVS_HASBUTTONS+&TVS_LINESATROOT,
                        0  0  200  400  D@Adressee  &NULL D§hInstance &NULL
            mov D§hwndTreeView eax
        call 'Comctl32.ImageList_Create' 16 16 &ILC_COLOR16 2 10
            mov D§hImageList eax
        call 'USER32.LoadBitmapA' D§hInstance IDB_TREE
            mov D§hBitmap eax
        call 'Comctl32.ImageList_Add' D§hImageList D§hBitmap &NULL
        call 'GDI32.DeleteObject' D§hBitmap
        call 'USER32.SendMessageA' D§hwndTreeView &TVM_SETIMAGELIST  0  D§hImageList

        call 'USER32.SendMessageA' D§hwndTreeView &TVM_INSERTITEM 0 TV_INSERTSTRUCT
            mov D§hParent eax, D§TVI_hParent eax
        mov D§TVI_hInsertAfter &TVI_LAST, D§TVI_pszText  Child1
        call 'USER32.SendMessageA' D§hwndTreeView &TVM_INSERTITEM 0  TV_INSERTSTRUCT
            mov D§TVI_pszText Child2
        call 'USER32.SendMessageA' D§hwndTreeView &TVM_INSERTITEM 0  TV_INSERTSTRUCT
 
    .Else_If D@Message = &WM_MOUSEMOVE
        ..If B§DragMode = &TRUE
            mov eax D@lParam | and eax 0ffff
            mov ecx D@lParam | shr ecx 16
            mov D§TVHIT_POINTx eax      ; eax is the horizontal position of the drag image
            mov D§TVHIT_POINTy ecx      ; ecx is the vertical position
            call 'Comctl32.ImageList_DragMove' eax ecx
            call 'Comctl32.ImageList_DragShowNolock' &FALSE
          ; check if an item is hit:
            call 'USER32.SendMessageA' D§hwndTreeView &TVM_HITTEST &NULL TVHITTESTINFO
            If eax ne &NULL
               call 'USER32.SendMessageA' D§hwndTreeView &TVM_SELECTITEM &TVGN_DROPHILITE eax
            End_If
            call 'Comctl32.ImageList_DragShowNolock' &TRUE
        ..End_If
 
    .Else_If D@Message = &WM_LBUTTONUP
        ..If B§DragMode = &TRUE
            call 'Comctl32.ImageList_DragLeave' D§hwndTreeView
            call 'Comctl32.ImageList_EndDrag'
            call 'Comctl32.ImageList_Destroy' D§hDragImageList
          ; Get the currently hilited item:
            call 'USER32.SendMessageA' D§hwndTreeView &TVM_GETNEXTITEM &TVGN_DROPHILITE 0
            call 'USER32.SendMessageA' D§hwndTreeView &TVM_SELECTITEM &TVGN_CARET eax
            call 'USER32.SendMessageA' D§hwndTreeView &TVM_SELECTITEM &TVGN_DROPHILITE 0
            call 'USER32.ReleaseCapture'
            mov B§DragMode &FALSE
        ..End_If
 
    .Else_If D@Message = &WM_NOTIFY
        mov edi D@lParam, eax D§NMHDR_code
        ..If D§edi+eax = &TVN_BEGINDRAG
 
            mov eax D§NM_itemNew | add eax D§TV_ITEMlen | add eax D§TV_ITEMhItem
            mov eax D§NMIN_hItem
            call 'USER32.SendMessageA' D§hwndTreeView &TVM_CREATEDRAGIMAGE 0 D§edi+eax
                mov D§hDragImageList,eax
            call 'Comctl32.ImageList_BeginDrag' D§hDragImageList 0 0 0
            mov eax D§NM_ptDragX, ebx D§NM_ptDragY
            call 'Comctl32.ImageList_DragEnter' D§hwndTreeView D§edi+eax  D§edi+ebx
            call 'USER32.SetCapture' D@Adressee
            mov B§DragMode &TRUE
        ..End_If

    .Else_If D@Message = &WM_DESTROY
        call 'USER32.PostQuitMessage' &NULL
    .Else
        popad
        call 'USER32.DefWindowProcA' D@Adressee D@Message D@wParam D@lParam
        Exit
    .End_If
 
    popad | mov eax &FALSE
EndP

Analysis:

Within WM_CREATE handler, you create the tree view control Note the styles. TVS_xxxx are the tree view specific styles. Next, you create an empty image list with will accept images of 16x16 pixels in size, 16-bit color and initially, it will contain 2 images but can be expanded to 10 if need arises. We then load the bitmap from the resource and add it to the image list just created. After that, we delete the handle to the bitmap since it will not be used anymore. When the image list is all set, we associate it with the tree view control by sending TVM_SETIMAGELIST to the tree view control. We insert items into the tree view control, beginning from the root item. Since it will be root item, hParent member is NULL and hInsertAfter is TVI_ROOT. imask member specifies that pszText, iImage and iSelectedImage members of the TV_ITEM structure is valid. We fill those three members with appropriate value. pszText contains the label of the root item, iImage is the index into the image in the image list that will be displayed to the left of the unselected item, and iSelectedImage is the index into the image in the image list that will be displayed when the item is selected. When all appropriate members are filled in, we send TVM_INSERTITEM message to the tree view control to add the root item to it. After the root item is added, we can attach the child items to it. hParent member is now filled with the handle of the parent item. And we will use identical images in the image list so we don't change iImage and iSelectedImage member. Now when the user tries to drag an item, the tree view control sends WM_NOTIFY message with TVN_BEGINDRAG as the code. lParam is the pointer to an NM_TREEVIEW structure which contains several pieces of information we need so we put its value into edi and use edi as the pointer to NM_TREEVIEW structure. assume edi:ptr NM_TREEVIEW is a way to tell MASM to treat edi as the pointer to NM_TREEVIEW structure. We then create a drag image by sending TVM_CREATEDRAGIMAGE to the tree view control. It returns the handle to the newly created image list with a drag image inside. We call ImageList_BeginDrag to set the hotspot in the drag image. Then we enter the drag operation by calling ImageList_DragEnter. This function displays the drag image at the specified location in the specified window. We use ptDrag structure that is a member of NM_TREEVIEW structure as the point where the drag image should be initially displayed.After that, we capture the mouse input and set the flag to indicate that we now enter drag mode. Now we concentrate on WM_MOUSEMOVE. When the user drags the drag image along, our parent window receives WM_MOUSEMOVE messages. In response to these messages, we update the drag image position with ImageList_DragMove. After that, we check if the drag image is over some item. We do that by sending TVM_HITTEST message to the tree view control with a point for it to check. If the drag image is over some item, we hilite that item by sending TVM_SELECTITEM message with TVGN_DROPHILITE flag to the tree view control. During the hilite operation, we hide the drag image so that it will not leave unsightly blots on the tree view control. When the user releases the left mouse button, the drag operation is at the end. We leave the drag mode by calling ImageList_DragLeave, followed by ImageList_EndDrag and ImageList_Destroy. To make the tree view items look good, we also check the last hilited item, and select it. We must also un-hilite it else the other items will not get hilited when they are selected. And lastly, we release the mouse capture.

[Iczelion's Win32 Assembly Homepage]