2 Attachment(s)
[vb6]Treeview - Prevent from indenting when no icon is used
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.
Attachment 152713
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. Edited. Can use TVM_GETITEMSTATE instead, passing result of pvGetTreeItem for wParam and ByVal &HFFFF& as the lParam in SendMessage
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.
Re: [vb6]Treeview - Prevent from indenting when no icon is used
FYI, for v5 of Common Controls (not tested on v6 OCX) you can use this more performant replacement for your `pvGetTreeItem` function
Code:
Private Function pvGetHItem(oNode As ComctlLib.Node) As Long
If Not oNode Is Nothing Then
Call CopyMemory(pvGetHItem, ByVal UnsignedAdd(ObjPtr(oNode), 68), 4)
End If
End Function
. . . where `UnsignedAdd` can be further replaced with `+` operator when project is not LARGEADDRESSAWARE flagged.
cheers,
</wqw>
Re: [vb6]Treeview - Prevent from indenting when no icon is used
Quote:
Originally Posted by
wqweto
FYI, for v5 of Common Controls (not tested on v6 OCX) you can use this more performant replacement for your `pvGetTreeItem` function...
Cool, kinda wondered if the hTreeItem was being cached by the Node class somewhere. Now we know where. Always a little leery using offsets that may not be backward or forward compatible. Since we may not be getting any more VB service packs any time in the future, that offset is unlikely to change. Testing for the offset, it it exists, with v6 of common controls should be simple enough.
Re: [vb6]Treeview - Prevent from indenting when no icon is used
Quote:
Originally Posted by
LaVolpe
Testing for the offset, it it exists, with v6 of common controls should be simple enough.
Agree, this is basicly what I did some time ago as the private member offset was not readily available for snatching somewhere from the "Internets".
cheers,
</wqw>
Re: [vb6]Treeview - Prevent from indenting when no icon is used
FYI: hTreeItem found at offset 68 for both versions of the common controls ocx
Re: [vb6]Treeview - Prevent from indenting when no icon is used
In the 1st post I said not to use any treeview style that includes icons or the checkbox style (v6 of common controls), but did not explain why.
Using any style that includes icons causes the indentation (reserved space) for icons, whether icons are assigned to a treeview item or not. That is the only reason.
Using the checkbox style conflicts with this API option. That is because internally, the treeview control is using the same API option to apply checkboxes as checked/unchecked from a hidden ImageList.
Edited: FYI, that hidden imagelist that VB uses to enable its checkbox option has 3 icons, the 1st is a fully transparent icon (required bogus entry), then a checked box, and an unchecked box. If any are curious, I got the imagelist using TVM_GETIMAGELIST, passing TVSIL_STATE. Then APIs to access the imagelist were required to draw/see them: ImageList_GetIcon, ImageList_Draw or DrawIconEx, & DestroyIcon.