Results 1 to 9 of 9

Thread: [RESOLVED] TreeView ... get Node index from Node hItem.

  1. #1

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Resolved [RESOLVED] TreeView ... get Node index from Node hItem.

    Ok, I was helping out yereverluvinuncleber and ran across a problem I haven't optimally solved.

    With the ListView, when you call SendMessage with LVM_HITTEST, you get the item's index returned.

    However, with the TreeView, when you call SendMessage with TVM_HITTEST, you get a handle (hItem), rather than the item's index.

    To further access the TreeView's node that we hit, I need the node's index (from the hItem).

    Here's some code I put together to get the index from the hItem:

    Code:
    
    Option Explicit
    '
    Private Declare Function SendMessageA Lib "user32" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
    '
    
    Public Function GetTreeNodeIndexFromHandle(tre As TreeView, hNode As Long)
        Dim i As Long
        Dim h As Long
        '
        For i = 1& To tre.Nodes.Count
            h = GetTreeNodeHandle(tre, tre.Nodes(i))
            If h = hNode Then
                GetTreeNodeIndexFromHandle = i
                Exit Function
            End If
        Next
        ' Returns ZERO if we fall out.
    End Function
    
    Public Function GetTreeNodeHandle(tre As TreeView, oNode As Node) As Long
        Const TVM_GETNEXTITEM       As Long = &H110A&
        Const TVGN_CARET            As Long = &H9&
        Dim selNode As Node
        '
        Set selNode = tre.SelectedItem
        Set tre.SelectedItem = oNode
        GetTreeNodeHandle = SendMessageA(tre.hWnd, TVM_GETNEXTITEM, TVGN_CARET, ByVal 0&)
        Set tre.SelectedItem = selNode
    End Function
    
    

    It works. However, after playing with it, I've discovered that it does cause a bit of flicker in the TreeView. The obvious reason for this is that the GetTreeNodeHandle procedure is briefly changing the selected node.

    I could develop some enumerative (and probably recursive) code to retrieve all the hItem values for a TreeView without changing the selection. However, that's more than a trivial amount of work. I'm just wondering if I'm overlooking something.

    Thanks,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  2. #2
    Fanatic Member
    Join Date
    Feb 2019
    Posts
    706

    Re: TreeView ... get Node index from Node hItem.

    It seems that it would be less code if you use TreeView.HitTest method, which returns a Node object, which has an Index property.

  3. #3

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: TreeView ... get Node index from Node hItem.

    Quote Originally Posted by qvb6 View Post
    It seems that it would be less code if you use TreeView.HitTest method, which returns a Node object, which has an Index property.
    qvb6, you might be right. But now I've gotten hard-headed and I want to figure this out. There's got to be a way (through API) to get from hItem to Index.

    I'm working on a recursive procedure now.

    Basically, the problem is getting the hItem of any Node we choose, without tampering with the selected node. In .NET, they fixed this (by supplying node handles), but I'd like to do it in VB6.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  4. #4

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: TreeView ... get Node index from Node hItem.

    Ok, I found some old code that did allow me to work through getting the Node object from the hItem (for the node). With this, it's trivial to get the index. This is the opposite of what I asked, but it kills two birds with one stone: 1) It eliminates the loop, and 2) It obviates the need to select the node (thereby eliminating flicker).

    Here's the code:

    Code:
    
    Option Explicit
    '
    Private Declare Function SendMessageA Lib "user32" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Dest As Any, ByRef Source As Any, ByVal Bytes As Long)
    Private Declare Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" (ByRef Dest As Any, ByVal length As Long, Optional ByVal Fill As Byte)
    '
    Private Type TVITEM
        mask As Long
        hitem As Long
        state As Long
        stateMask As Long
        pszText As Long    ' if a string, must be pre-allocated!!
        cchTextMax As Long
        iImage As Long
        iSelectedImage As Long
        cChildren As Long
        lParam As Long
    End Type
    '
    
    Public Function GetTreeNodeFromHandle(tre As TreeView, hNode As Long) As Node
        ' For both the Mscomctl.ocx and Comctl32.ocx TreeView and ListView controls,
        ' the Node and ListItem's ObjPtr() values reside at the 3rd DWORD
        ' (byte offset 8) in the Node's and ListItem's lParam.
        '
        Const TVIF_PARAM    As Long = &H4&
        Const TVM_GETITEM   As Long = &H110C&
        Dim tvi             As TVITEM
        Dim pNode           As Long
        Dim oNode           As Node
        '
        tvi.hitem = hNode
        tvi.mask = TVIF_PARAM
        If SendMessageA(tre.hWnd, TVM_GETITEM, 0&, tvi) = 0& Then Exit Function
        If tvi.lParam = 0& Then Exit Function
        '
        CopyMemory pNode, ByVal tvi.lParam + 8&, 4&
        If pNode = 0& Then Exit Function
        '
        CopyMemory oNode, pNode, 4&
        Set GetTreeNodeFromHandle = oNode
        FillMemory oNode, 4&, CByte(0)    ' Clean-up.
    End Function
    
    
    

    This one's resolved.

    Thanks,
    Elroy


    EDIT1: And here's the inverse in case anyone needs it. This is nice in that it effectively provides a "Handle" property to our TreeView nodes:

    Code:
    
    Private Function hItemFromNode(ByVal oNode As Node) As Long
        If (oNode Is Nothing) = False Then CopyMemory hItemFromNode, ByVal (ObjPtr(oNode) + 68&), 4&
    End Function
    
    
    Last edited by Elroy; Jun 22nd, 2019 at 06:56 PM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  5. #5
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,120

    Re: [RESOLVED] TreeView ... get Node index from Node hItem.

    Nice!

    Didn't know this and I used to loop Nodes collection and get hItem for each node like this

    thinBasic Code:
    1. Private Function pvGetNode(ByVal hItem As Long) As ComctlLib.Node
    2.     Dim lTmp            As Long
    3.  
    4.     For Each pvGetNode In m_oCtl.Nodes
    5.         Call CopyMemory(lTmp, ByVal UnsignedAdd(ObjPtr(pvGetNode), 68), PTR_SIZE)
    6.         If lTmp = hItem Then
    7.             Exit Function
    8.         End If
    9.     Next
    10. End Function
    TVIF_PARAM trick helps me get rid of the loop in pvGetNode

    thinBasic Code:
    1. Private Function pvGetNode(ByVal hItem As Long) As ComctlLib.Node
    2.     Dim uItem           As TV_ITEM
    3.     Dim lPtr            As Long
    4.    
    5.     If hItem <> 0 Then
    6.         With uItem
    7.             .hItem = hItem
    8.             .mask = TVIF_PARAM
    9.             Call SendMessage(m_hWndCtl, TVM_GETITEM, 0, uItem)
    10.             If .lParam <> 0 Then
    11.                 Call CopyMemory(lPtr, ByVal UnsignedAdd(.lParam, 8), PTR_SIZE)
    12.                 Call vbaObjSetAddref(pvGetNode, lPtr)
    13.             End If
    14.         End With
    15.     End If
    16. End Function
    17.  
    18. Private Function pvGetHItem(oNode As ComctlLib.Node) As Long
    19.     If Not oNode Is Nothing Then
    20.         Call CopyMemory(pvGetHItem, ByVal UnsignedAdd(ObjPtr(oNode), 68), PTR_SIZE)
    21.     End If
    22. End Function
    cheers,
    </wqw>

  6. #6
    New Member
    Join Date
    Oct 2020
    Posts
    4

    Re: [RESOLVED] TreeView ... get Node index from Node hItem.

    Registered just to say Thank you, to Elroy.
    I got stuck after HitTest returns hwnd of TV Item. Can't find a way to convert hwnd to Node.
    Really appreciated sharing your code.

  7. #7
    New Member
    Join Date
    Jan 2021
    Posts
    2

    Re: [RESOLVED] TreeView ... get Node index from Node hItem.

    In my case , the offset to the handle is 72 , not 68

    CopyMemory hItemFromNode, ByVal (ObjPtr(oNode) + 72&), 4&

  8. #8
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,120

    Re: [RESOLVED] TreeView ... get Node index from Node hItem.

    Quote Originally Posted by Hussainsat View Post
    In my case , the offset to the handle is 72 , not 68

    CopyMemory hItemFromNode, ByVal (ObjPtr(oNode) + 72&), 4&
    This sucks!

    I'm using this with a reference to Microsoft Windows Common Controls 5.0 (SP2) in C:\Windows\SysWOW64\COMCTL32.OCX with OCX file version 6.0.81.5

    Is it with some other OCX version of Microsoft Windows Common Controls 5.0 (SP2) that this offset has been changed?

    cheers,
    </wqw>

  9. #9
    New Member
    Join Date
    Jan 2021
    Posts
    2

    Re: [RESOLVED] TreeView ... get Node index from Node hItem.

    Quote Originally Posted by wqweto View Post
    This sucks!

    I'm using this with a reference to Microsoft Windows Common Controls 5.0 (SP2) in C:\Windows\SysWOW64\COMCTL32.OCX with OCX file version 6.0.81.5

    Is it with some other OCX version of Microsoft Windows Common Controls 5.0 (SP2) that this offset has been changed?

    cheers,
    </wqw>
    My OCX is C:\Windows\SysWOW64\MSCOMCTL.OCX
    Microsoft Windows Common Controls 6.0 (SP6)
    Name:  Untitled.jpg
Views: 476
Size:  86.6 KB


    I wrote some code to scan for the offset ..

    Code:
    Function FindOffsetToVal(Obj As Node, Val As Long) As Long
    
        Dim Offset&, Temp&
         
        For Offset = 0 To 100
        
            CopyMemory Temp, ByVal (ObjPtr(Obj) + Offset), 4&
            If Temp = Val Then
                FindOffsetToVal = Offset
                Exit For
           End If
            
        Next
    
    End Function

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width