Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1469

[vb6]Treeview - Prevent from indenting when no icon is used

$
0
0
Just a neat option. It has its limitations. It works for both versions 5 & 6 of the common controls TreeView.

In the screenshot below, you'll notice that the left image has indentation, or reserved white space for icons, when icons (ImageList) are used and no icon is assigned. Looks kinda ugly. But we can avoid this with a little API help.

Name:  treeIcons.png
Views: 117
Size:  7.2 KB

Limitations:
1. You cannot use any treeview style that includes icons, i.e., not tvwTreeLinesPictureText
2. You cannot use the checkbox style (image above uses icons vs checkbox style)
3. The number of different icons you can use is limited to 15 maximum
4. You must add a bogus icon in the ImageList. This bogus icon is always the 1st one

The reason for 15 max icons is that this API option only allows 4 bits to identify an icon index. With only 4 bits, we have a maximum range of 0 to 15. The value 0 is used to clear the icon, values 1 thru 15 are the possible icons in your image list, starting with the 2nd icon in the list. So, consider the icons in the imagelist as zero-bound even though you can reference them directly as one-bound. Because they are considered zero-bound, and zero index is used to clear the icon, the 1st icon in the image list is simply not used.

Here are the APIs used
Code:

Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
Private Const TV_FIRST As Long = &H1100
Private Const TVM_GETITEMA As Long = (TV_FIRST + 12)
Private Const TVM_SETITEMA As Long = (TV_FIRST + 13)
Private Const TVM_GETNEXTITEM As Long = (TV_FIRST + 10)
Private Const TVM_GETITEMRECT As Long = (TV_FIRST + 4)
Private Const TVM_SETIMAGELIST As Long = (TV_FIRST + 9)
Private Const TVSIL_STATE As Long = 2
Private Const TVIF_STATE As Long = &H8
Private Const TVGN_ROOT As Long = &H0
Private Const TVGN_CHILD As Long = &H4
Private Const TVGN_NEXT As Long = &H1
Private Const TVGN_CARET As Long = &H9

The image list must be assigned via this call. Should be added in Form_Load.
Note that you still associate the ImageList via the Treeview property page, as normal.
Code:

' change Treeview1 & ImageList1 as needed
    SendMessage TreeView1.hWnd, TVM_SETIMAGELIST, TVSIL_STATE, ByVal ImageList1.hImageList

This helper function is used by the other 3 public functions. Purpose is to retrieve the node ID (assigned by the window) not the index/key assigned by the control. It does this by reverse navigating from the target node to the 1st (root) node in the tree.
Edited: See post #2 for an alternate method of retrieving the Node ID
Code:

Private Function pvGetTreeItem(tView As TreeView, Node As Node) As Long

    Dim rNode As Node, tNode As Node
    Dim cMoves As Collection
    Dim c As Long, hItem As Long
   
    If tView.SelectedItem Is Node Then ' quick access
        hItem = SendMessage(tView.hWnd, TVM_GETNEXTITEM, TVGN_CARET, ByVal 0&)
    ElseIf Node Is Node.Root Then ' quick access
        hItem = SendMessage(tView.hWnd, TVM_GETNEXTITEM, TVGN_ROOT, ByVal 0&)
    Else
        Set cMoves = New Collection
       
        Set rNode = Node.Root                  ' used for reference
        Do Until tNode.Parent Is Nothing        ' navigate up the tree from passed node
            Set rNode = tNode.Parent.Child      ' used for reference
            Do Until tNode Is rNode
                cMoves.Add TVGN_NEXT            ' inverse navigate using NEXT
                Set tNode = tNode.Previous
            Loop
            cMoves.Add TVGN_CHILD              ' inverse navigate using CHILD
            Set tNode = tNode.Parent
        Loop
        Set tNode = Node                        ' used for reference
        If Not tNode Is rNode Then              ' at leaf top level, if not root, continue
            Do Until tNode Is rNode
                cMoves.Add TVGN_NEXT            ' inverse navigate using NEXT
                Set tNode = tNode.Previous
            Loop
        End If
        ' now navigate to the desired node from the root
        hItem = SendMessage(tView.Hwnd, TVM_GETNEXTITEM, TVGN_ROOT, ByVal 0&)
        For c = cMoves.Count To 1 Step -1
            hItem = SendMessage(tView.Hwnd, TVM_GETNEXTITEM, cMoves(c), ByVal hItem)
            If hItem = 0 Then Exit For
        Next
        Set cMoves = Nothing
    End If
    pvGetTreeItem = hItem

End Function

Here are three functions that do what we'll want. Can be placed in a module or your form
1. Setting the icon from the image list
Code:

Public Sub SetNodeIcon(tView As TreeView, Node As Node, ZeroBoundIconIndex As Long)

    If Node Is Nothing Or tView Is Nothing Then Exit Sub
    If ZeroBoundIconIndex < 0 Then Exit Sub
    If ZeroBoundIconIndex > 15 Then Exit Sub

    Dim lAttr(0 To 10) As Long
   
    lAttr(1) = pvGetTreeItem(tView, Node)
    If lAttr(1) Then
        lAttr(0) = TVIF_STATE
        lAttr(3) = &HFFFF&
        SendMessage tView.hWnd, TVM_GETITEMA, 0&, lAttr(0)
        lAttr(2) = (lAttr(2) And &HFFFF0FFF) Or (&H1000& * ZeroBoundIconIndex)
        SendMessage tView.hWnd, TVM_SETITEMA, 0&, lAttr(0)
    End If
   
End Sub

2. Retrieving which icon is assigned
Code:

Public Function GetNodeIcon(tView As TreeView, Node As Node) As Long

    If Node Is Nothing Or tView Is Nothing Then Exit Function

    Dim lAttr(0 To 10) As Long
   
    lAttr(1) = pvGetTreeItem(tView, Node)
    If lAttr(1) Then
        lAttr(0) = TVIF_STATE
        lAttr(3) = &HFFFF&
        SendMessage tView.hWnd, TVM_GETITEMA, 0&, lAttr(0)
        GetNodeIcon = (lAttr(2) And &HF000&) \ &H1000&
    End If

End Function

3. This is optional. A method to determine if user is clicking on the icon. See the sample project to see how the Node_Click event uses this method.
Code:

Public Function MouseOverNodeIcon(tView As TreeView, Node As Node, x As Single) As Boolean
   
    ' X must be passed in pixels
   
    If Node Is Nothing Or tView Is Nothing Then Exit Function
   
    Dim tRect(0 To 3) As Long
   
    tRect(0) = pvGetTreeItem(tView, Node)
    If tRect(0) Then
        If SendMessage(tView.hWnd, TVM_GETITEMRECT, 1&, tRect(0)) Then
            MouseOverNodeIcon = (x < tRect(0))
        End If
    End If
End Function

In the sample project, you can click on the icons to toggle the "checkmark"
Oops. Left in some test code. Re-uploaded the zip to fix that.
Attached Images
 
Attached Files

Viewing all articles
Browse latest Browse all 1469

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>