Page 1 of 1

MASM - Creating a TreeView with "Option" buttons

#1 GunnerInc  Icon User is online

  • "Hurry up and wait"
  • member icon




Reputation: 862
  • View blog
  • Posts: 2,308
  • Joined: 28-March 11

Posted 05 November 2011 - 05:59 PM

Everyone knows what a Treeview is - Windows Explorer uses a treeview for the left pane and Windows and other programs use them to set options with a treeview and option buttons. I will discuss creating and using a treeview with option buttons, why? Years ago, when I started there wasn't really any samples for Win32 Assembly using a treeview with options buttons. Plus, I like it :)
Attached Image
Complete code and sample app in attached zip.

Ok, to use images we need to create an image list we do this by calling ImageList_Create and save the returned handle:
 invoke	ImageList_Create, 16, 16, ILC_COLOR32 or ILC_MASK, 4, 4
    mov		  himlTree, eax


Our icons are 16x16, and only need 4 images

Then we have to load the icons from our resource section and add to the ImageList:
In the source, we do this 4 times: 1 for the apple icon, 1 for the orange icon, 1 for option on icon and 1 for option off icon
    invoke	LoadImage, hInst, ICON_ORANGE, IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR
    push	  eax										
    invoke	ImageList_AddIcon, himlTree, eax
    call	  DestroyIcon


We do this for each icon we want to use in the Treeview. Technically, we don't have to call DestroyIcon since the system automatically frees an icon resource when no longer needed, but I like to free things when I don't need them.

We create the treeview in the WM_CREATE handler as any other control:
      invoke	CreateWindowEx, \
                  WS_EX_CLIENTEDGE, \
                  offset szWndTreeView, \
                  NULL, \
                  WS_CHILD or WS_VISIBLE or WS_TABSTOP or TVS_DISABLEDRAGDROP, \
                  5, 5, \
                  255, 280, \
                  hWin, TV_OPTIONS, \
                  hInst, NULL
      mov		hTVOptions, eax


In order to use "option" buttons, we need to tell the treeview where to get the icons from, we do that with SendMessage:
    invoke	SendMessage, eax, TVM_SETIMAGELIST, TVSIL_NORMAL, himlTree


There are two types if Image lists the treeview can use:
Normal - contains selected, nonselected and overlay images
State- indicate app defined item state
We are only interested in the Normal imagelist.

Now we will insert 2 Parents (Root Nodes) with 6 children each
PutFruitOnDaTree proc uses edi ebx esi
LOCAL	tvis:TV_INSERTSTRUCT 

    ;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    ;%%%%%	Options Treeveiw		%%%%%
    ;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    ;#####	Apples parent
    mov		tvis.item.iImage, 0
    mov		tvis.item.iSelectedImage, 0
    mov		tvis.hParent, 0
    mov		tvis.hInsertAfter, TVI_SORT
    mov		tvis.item.imask, TVIF_TEXT\
                    or TVIF_IMAGE\
                    or TVIF_STATE
    mov		tvis.item.state, TVIS_BOLD or TVIS_EXPANDED 
    mov		tvis.item.stateMask, TVIS_BOLD or TVIS_EXPANDED 
    mov		tvis.item.pszText, offset szApples
    invoke	SendMessage, hTVOptions, TVM_INSERTITEM, 0, addr tvis

    ;#####	Add dem apples
    mov		tvis.hParent, eax
    mov		tvis.item.imask, TVIF_TEXT or TVIF_IMAGE
    mov		tvis.item.iImage, OPTION_OFF

    xor		ebx, ebx
    lea		edi, offset APPLE_ARRAY
    .while ebx <= APPLE_ARRAY_SIZE - 1
        mov		eax, [edi + 4 * ebx]
        mov		tvis.item.pszText, eax
        invoke	SendMessage, hTVOptions, TVM_INSERTITEM, 0, addr tvis
        inc		ebx
    .endw
    
    ;##### Oranges
    mov		tvis.item.iImage, 1
    mov		tvis.item.iSelectedImage, 1
    mov		tvis.hParent, 0
    mov		tvis.hInsertAfter, TVI_SORT
    mov		tvis.item.imask, TVIF_TEXT\
                    or TVIF_IMAGE\
                    or TVIF_STATE
    mov		tvis.item.state, TVIS_BOLD or TVIS_EXPANDED 
    mov		tvis.item.stateMask, TVIS_BOLD or TVIS_EXPANDED 
    mov		tvis.item.pszText, offset szOranges
    invoke	SendMessage, hTVOptions, TVM_INSERTITEM, 0, addr tvis
    
    ;#####	Add dem oranges
    mov		tvis.hParent, eax
    mov		tvis.item.imask, TVIF_TEXT or TVIF_IMAGE
    mov		tvis.item.iImage, OPTION_OFF
    
    xor		ebx, ebx
    lea		edi, offset ORANGE_ARRAY
    .while ebx <= ORANGE_ARRAY_SIZE - 1
        mov		eax, [edi + 4 * ebx]
        mov		tvis.item.pszText, eax
        invoke	SendMessage, hTVOptions, TVM_INSERTITEM, 0, addr tvis
        inc		ebx
    .endw
    
    ret
PutFruitOnDaTree endp


Break Down:
How does the treeview know what item is a root node or child? We tell the treeview what is what by filling the TV_INSERTSTRUCT
To add the parent (root node)
    mov		tvis.item.iImage, 0
    mov		tvis.item.iSelectedImage, 0
    mov		tvis.hParent, 0
    mov		tvis.hInsertAfter, TVI_SORT
    mov		tvis.item.imask, TVIF_TEXT\
                    or TVIF_IMAGE\
                    or TVIF_STATE
    mov		tvis.item.state, TVIS_BOLD or TVIS_EXPANDED 
    mov		tvis.item.stateMask, TVIS_BOLD or TVIS_EXPANDED 
    mov		tvis.item.pszText, offset szApples
    invoke	SendMessage, hTVOptions, TVM_INSERTITEM, 0, addr tvis

.item.image is the image for the item, in this case the parent node is an apple
.item.iSelectedImage is the image to display when we select the item, in this case we want it to remain an apple
.hParent - we set to zero to inform the tree to insert as a root node
.hInsertAfter - I want the tree to sort the items so we use TVI_SORT valid options are: TVI_FIRST, TVI_LAST, TVI_ROOT and TVI_SORT
.item.imask tells the tree what members of the structure we are filling in. We are going to give it text, an image, selected image, and want to set the state of the item
.item.state I want the root node to be bold, and to automatically expand
.item.stateMask should mirror what you put for .state
.item.pszText is a pointer to the text you want displayed.

And for children:
    ;#####	Add dem apples
    mov		tvis.hParent, eax
    mov		tvis.item.imask, TVIF_TEXT or TVIF_IMAGE
    mov		tvis.item.iImage, OPTION_OFF

    xor		ebx, ebx
    lea		edi, offset APPLE_ARRAY
    .while ebx <= APPLE_ARRAY_SIZE - 1
        mov		eax, [edi + 4 * ebx]
        mov		tvis.item.pszText, eax
        invoke	SendMessage, hTVOptions, TVM_INSERTITEM, 0, addr tvis
        inc		ebx
    .endw

When we insert a root node, its handle is returned in eax
.hParent is the handle of this childs parent (every child should have a parent) ;)
.item.imask we don't want to use the previous mask, because we only need to set the text and image
.item.iImage is our option off icon
for the example, I created an array of pointer to some text I wanted to use for the children, I just loop through the array and add each child
I want to add another root node and children under it so I just repeat that process.

There is another member of the TV_INSERTSTRUCT that you might/will use and that is the .item.lParam. This is a DWORD value. You can store a number to associate with this item or a pointer to a string to associate with the item.
You can then use this value for a setting in a file if item is selected.

Ok, the magic of setting the "option" button to "on" happens in the WM_NOTIFY:
.elseif eax == WM_NOTIFY
            cmp		wParam, TV_OPTIONS
            jne		PassThrough

            mov		esi, lParam
            mov		ecx, (NMHDR ptr [esi]).code
            cmp		ecx, TVN_SELCHANGING
            jne		PassThrough

            ; Get parent of new item
            invoke	SendMessage, hTVOptions, TVM_GETNEXTITEM, TVGN_PARENT, (NMTREEVIEW ptr [esi]).itemNew.hItem
            test	eax, eax
            jz		Done 	; Parent clicked leave
            
            ; Clear option images 
            invoke	ClearNode, eax

            ; Set Image to "on" for selected item
            mov		ecx, (NMTREEVIEW ptr [esi]).itemNew.hItem
            mov		tvi.imask, TVIF_IMAGE or TVIF_HANDLE or TVIF_SELECTEDIMAGE
            mov		tvi.hItem, ecx
            mov		tvi.iImage, OPTION_ON
            mov		tvi.iSelectedImage, OPTION_ON	
            invoke	SendMessage, hTVOptions, TVM_SETITEM, 0, addr tvi
            
            cmp		wParam, TV_OPTIONS
            jne		PassThrough

Is the Notification coming from our treeview? No? Then pass the message on to the default proc:
    PassThrough:
        invoke DefWindowProc, hWin, uMsg, wParam, lParam

            mov		esi, lParam
            mov		ecx, (NMHDR ptr [esi]).code
            cmp		ecx, TVN_SELCHANGING
            jne		PassThrough

Here we inspect the messages coming in, we only want to intercept the message for Selection changing, if it is not the message, then pass it on.
In Notify messages lParam will contain a pointer to a NMHDR structure so we save that pointer to esi for ease of use.

            invoke	SendMessage, hTVOptions, TVM_GETNEXTITEM, TVGN_PARENT, (NMTREEVIEW ptr [esi]).itemNew.hItem
            test	eax, eax
            jz		Done 	; Parent clicked leave


If the item being selected is a parent, leave. We don't want to change the parent icon (in this sample)

    invoke	ClearNode, eax

"Un-select" any selected option buttons, code is in the attachment. It just loops through each child of the current parent and sets the icon to "Off"
            ; Set Image to "on" for selected item
            mov		ecx, (NMTREEVIEW ptr [esi]).itemNew.hItem
            mov		tvi.imask, TVIF_IMAGE or TVIF_HANDLE or TVIF_SELECTEDIMAGE
            mov		tvi.hItem, ecx
            mov		tvi.iImage, OPTION_ON
            mov		tvi.iSelectedImage, OPTION_ON	
            invoke	SendMessage, hTVOptions, TVM_SETITEM, 0, addr tvi


Here we use a TVITEM structure to tell the treeview what items image we want to change and to change to the "Item On" icon.

Wow, great! But how can we find out which "child" was selected? I dunno, was hoping you knew ;)
There is no simple way to go through a tree, for this example, I modified some code I wrote back in '03 that enumerates a trees roots, parents, and children.
Basically you:
1. Get the handle to the first parent and save it.
2. Get the handle of the parents first child and save it.
3. Loop through the rest of children under current parent.
4. Get handle to next parent and save it.
5. Get the handle of the parents first child and save it.
6. Loop through the rest of children under current parent.
7. Repeat step 4 till no more

ShowSelected proc uses ebx esi edi
LOCAL	mbp:MSGBOXPARAMS
Local	TempBuffer[20]:BYTE
local	tvi:TVITEM


xor		esi, esi

In this, we will use esi counter for number selected
 mov		byte ptr [lpszTotal], 0

Place a NULL in our buffer to "clear it out"

invoke	szCatStr, offset lpszTotal, offset szSelApple

This is a function from the MASM32 Library that will append the second string to the first

  ;#####	Get handle of first root
    invoke	SendMessage, hTVOptions, TVM_GETNEXTITEM, TVGN_ROOT, NULL
    mov		edi, eax

First we need to get the handle to the first root node and save it for later use.

    ;#####	Get handle of first child
    invoke	SendMessage, hTVOptions, TVM_GETNEXTITEM, TVGN_CHILD, eax
    mov		ebx, eax

Next we get the handle to the first nodes child and save it for later use

    ;#####	See if selected
    lea		ecx, TempBuffer
    mov		byte ptr [ecx], 0
    mov		tvi.imask, TVIF_IMAGE or TVIF_TEXT
    mov		tvi.hItem, eax
    mov		tvi.pszText, ecx
    mov		tvi.cchTextMax, 20
    invoke	SendMessage, hTVOptions, TVM_GETITEM, 0, addr tvi

    .if tvi.iImage == OPTION_ON
        invoke	szMultiCat, 2, offset lpszTotal, addr TempBuffer, offset szCRLF
        inc		esi
        jmp		GetOranges
    .endif

We fill in a few members of the TVITEM structure telling the treeview what info we want. In this case we want the text and the index of the image in the image list. We then check the index of the image and if it is the OPTION_ON icon, we add the childs text to our buffer and then jump to GetOranges, if not, we get the next child.
.pszText is a pointer to the buffer to receive the text
.cchTextMax is the size of our buffer to receive the text
NextAppleChild:
    ;#####	Loop through the rest of the apples
    invoke	SendMessage, hTVOptions, TVM_GETNEXTITEM, TVGN_NEXT, ebx
    .if eax == 0
        invoke	szCatStr, offset lpszTotal, offset szCRLF
        jmp		GetOranges
    .else
        mov		ebx, eax
        ;#####	See if selected
        lea		ecx, TempBuffer
        mov		byte ptr [ecx], 0
        mov		tvi.imask, TVIF_IMAGE or TVIF_TEXT
        mov		tvi.hItem, eax
        mov		tvi.pszText, ecx
        mov		tvi.cchTextMax, 20
        invoke	SendMessage, hTVOptions, TVM_GETITEM, 0, addr tvi
    
        .if tvi.iImage == OPTION_ON
            invoke	szMultiCat, 2, offset lpszTotal, addr TempBuffer, addr szCRLF
            inc		esi
            jmp		GetOranges
        .endif
        jmp		NextAppleChild
    .endif

We get the next child by passing the handle of the previous child to the treeview with SendMessage, we save the returned handle for the next call to get the next child. We loop until SendMessages returns 0 checking the index of the childs image and if it is the ON icon, append the text to our buffer and jump to GetOrangs if not, get and check the next child.

The following is the exact same as above to get the selected orange, except we pass the handle of the first root in the first call to SendMessage to get the next root node.
GetOranges:
    invoke	szCatStr, offset lpszTotal, offset szSelOrange
    invoke	SendMessage, hTVOptions, TVM_GETNEXTITEM, TVGN_NEXT, edi
    ;#####	Get handle of first child
    invoke	SendMessage, hTVOptions, TVM_GETNEXTITEM, TVGN_CHILD, eax
    mov		ebx, eax
    
    ;#####	See if selected
    lea		ecx, TempBuffer
    mov		byte ptr [ecx], 0
    mov		tvi.imask, TVIF_IMAGE or TVIF_TEXT
    mov		tvi.hItem, eax
    mov		tvi.pszText, ecx
    mov		tvi.cchTextMax, 20
    invoke	SendMessage, hTVOptions, TVM_GETITEM, 0, addr tvi

    .if tvi.iImage == OPTION_ON
        invoke	szCatStr, offset lpszTotal, addr TempBuffer
        inc		esi
        jmp		NoMoreOranges
    .endif
    
NextOrangeChild:
    ;#####	Loop through the rest of the apples
    invoke	SendMessage, hTVOptions, TVM_GETNEXTITEM, TVGN_NEXT, ebx
    .if eax == 0
        jmp		NoMoreOranges
    .else
        mov		ebx, eax
        ;#####	See if selected
        lea		ecx, TempBuffer
        mov		byte ptr [ecx], 0
        mov		tvi.imask, TVIF_IMAGE or TVIF_TEXT
        mov		tvi.hItem, eax
        mov		tvi.pszText, ecx
        mov		tvi.cchTextMax, 20
        invoke	SendMessage, hTVOptions, TVM_GETITEM, 0, addr tvi
    
        .if tvi.iImage == OPTION_ON
            invoke	szCatStr, offset lpszTotal, addr TempBuffer
            inc		esi
            jmp		NoMoreOranges
        .endif
        jmp		NextOrangeChild
    .endif


NoMoreOranges:	
    mov		mbp.cbSize, sizeof MSGBOXPARAMS
    push	hMain
    pop		mbp.hwndOwner
    push	hInst
    pop		mbp.hInstance
    mov		mbp.lpszIcon, ICON_APPLE
    .if	esi == 0
        mov		mbp.lpszText, offset szNothingSelected
    .else
        mov		mbp.lpszText, offset lpszTotal		
    .endif
    mov		mbp.lpszCaption, offset szMsgBoxCaption
    mov		mbp.dwStyle, MB_USERICON or MB_OK
    invoke	MessageBoxIndirect, addr mbp
    ret
ShowSelected endp


MessageBoxIndirect is a MessageBox that can be customized, here I use the apple Icon.

Attached File(s)



Is This A Good Question/Topic? 0
  • +

Page 1 of 1