Tutorial 18: Common Controls | Tutorial 19: Tree View Control | Tutorial 20: Window Subclassing |
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.
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.
NMHDR STRUCT DWORD
hwndFrom DWORD ?
idFrom DWORD ?
code DWORD ?
NMHDR ENDS
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
TV_INSERTSTRUCT STRUCT DWORD
hParent DWORD ?
hInsertAfter DWORD ?
ITEMTYPE <>
TV_INSERTSTRUCT ENDS
ITEMTYPE UNION
itemex TVITEMEX <>
item TVITEM <>
ITEMTYPE ENDS
We will use only TVITEM here.
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
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
This function returns the handle to an empty image list if successful.
ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD
This function returns -1 if unsuccessful.
TVM_GETIMAGELIST
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.
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.
imagelist_begindrag PROTO himlTrack:DWORD, \
iTrack:DWORD, \
dxHotspot:DWORD, \
dyHotspot:DWORD
ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD
.386
.model FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.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
; 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
mov hwndTreeView,eax
; create the associated image list
invoke ImageList_Create,16,16,ILC_COLOR16,2,10
mov hImageList,eax
invoke LoadBitmap,hInstance,IDB_TREE ; load the bitmap from
; the resource
mov hBitmap,eax
; add the bitmap into the image list
invoke ImageList_Add,hImageList,hBitmap,NULL
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
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
Note the styles. TVS_xxxx are the tree view specific styles.
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
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.
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
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 th
e root item to it.
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
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.
.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
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.
.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
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.
.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
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.
Tutorial 18: Common Controls | Overview | Tutorial 20: Window Subclassing |