Ok, when you hover (just mouse over) items in a TreeView, they underline the item you're hovering.
I'd like an event that'd tell me when that's happening, and preferably another event (or the same one with a flag) that tells me when it's no longer happening.
If you must know why I want this, I'm building a ToolTip system for the TreeView (based on the name/text of specific nodes). I've got it working, but the HitTest doesn't work as well as I'd like. Therefore, I'm wanting this "hover/underlining" information to improve it.
I'm sure I could figure this out by monitoring messages with subclassing, but I thought I'd ask if anyone has already done it before I did all that work.
Thanks,
Elroy
EDIT: Just as an FYI, the HitTest works, but it "hits" when you're over any part of the node's row (and not just over the actual text). In fact, frustratingly, it even "hits" when you're over the vertical scrollbar.
Also, another alternative would be some way to dig out whether or not a node is "activated" (not selected, but activated from that hovering). If I could figure that out with some TreeView API call, I wouldn't need to subclass.
EDIT2: Also, I just figured out that I'm getting that behavior because I have "HotTracking" turned on. But that's all good. Now, I just need to figure out which node is "hot", if any.
Last edited by Elroy; Jan 10th, 2022 at 12:53 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.
Yeah, I've already been playing around with subclassing.
If I understand it correctly, the TVN_GETINFOTIPA (or TVN_GETINFOTIPW) message comes through the NMHDR structure of WM_NOTIFY messages.
I've been monitoring WM_NOTIFY, but I'm not getting either of those sub-messages. All I'm getting is a -520 and -521, and those supposedly aren't even TreeView messages: TVN_FIRST=-400 and TVN_LAST=-499.
I also saw the TVS_INFOTIP style bit. I don't seem to have an explicit property for it, so maybe I need to set it with an API call. Argh.
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.
Just to show it, here's some code that's in my subclass procedure:
Code:
Dim uHeader As NMHDR
Const WM_NOTIFY As Long = &H4E&
Select Case uMsg
Case WM_NOTIFY
CopyMemory uHeader, ByVal lParam, LenB(uHeader)
Debug.Print uHeader.code
End Select
And here's what I'm using for that NMHDR structure:
Code:
Private Type NMHDR
hwndFrom As Long
idFrom As Long
code As Long
End Type
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.
Hmmm, I turned on that TVS_INFOTIP style but I'm still not getting any TVN_GETINFOTIPA or TVN_GETINFOTIPW messages. Still just getting -520.
Here's the code I used to change the style:
Code:
' Turn on the TreeView's TVS_INFOTIP style.
Const TVS_INFOTIP As Long = &H800&
Const GWL_STYLE As Long = &HFFFFFFF0
Debug.Print Hex$(GetWindowLongA(tvw.hWnd, GWL_STYLE))
SetWindowLongA tvw.hWnd, GWL_STYLE, GetWindowLongA(tvw.hWnd, GWL_STYLE) Or TVS_INFOTIP
Debug.Print Hex$(GetWindowLongA(tvw.hWnd, GWL_STYLE))
The style is definitely changing, from 0x56001227 to 0x56001A27, which looks right to me.
If I can't figure it out soon, I'll take the time to cut-out a demo/test project. Right now, I'm just trying to do it in my larger project.
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.
When you're subclassing a VB6 control instead of an API control, you need to trap OCM_NOTIFY instead of WM_NOTIFY:
Code:
Public Const OCM__BASE = (WM_USER + &H1C00)
Public Const OCM_NOTIFY = (OCM__BASE + WM_NOTIFY)
Then you'd copy it right into NMTREEVIEW:
Code:
Public Type NMTREEVIEW
hdr As NMHDR
Action As Long
itemOld As TVITEM
itemNew As TVITEM
ptDrag As POINTAPI
End Type
Case OCM_NOTIFY
Dim nmtv As NMTREEVIEW
CopyMemory nmtv, ByVal lParam, Len(nmtv)
Select Case nmtv.hdr.Code
Note that you'd also be doing that in a WndProc you subclass TreeView1.hwnd to, not the parent form like with an API-created control.
Last edited by fafalone; Jan 10th, 2022 at 03:14 PM.
When you're subclassing a VB6 control instead of an API control, you need to trap OCM_NOTIFY instead of WM_NOTIFY:
Ahhh, yes, I was seeing those and ignored them. I'll change my first test to OCM_NOTIFY.
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.
Just in case you missed my edit just wanted to add you trap OCM_NOTIFY in a WndProc you subclass TreeView1.hWnd to; not the parent like if the control was API created.
Oh yes, Fafalone, I'm on top of that. I'm now getting my TVN_GETINFOTIPA (413) messages with the following:
Code:
Const OCM_NOTIFY As Long = 8270&
Dim uHeader As NMTREEVIEW
Select Case uMsg
Case OCM_NOTIFY
CopyMemory uHeader, ByVal lParam, LenB(uHeader)
Debug.Print uHeader.hdr.code
End Select
I believe I can work with this. I was using ..._MouseMove which was giving me my mouse's X,Y ... and now I won't have that. But I can figure that out easy enough.
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.
Ok, turns out I didn't even need the x,y. Displaying tips doesn't need it as it figures it out internally.
But WOW, this was a mess to get going the way I wanted. Also, it turns out I did not want the TVS_INFOTIP style bit turned on. That changed the "hot" behavior in a very undesirable way.
Also, the TVN_GETINFOTIP message wasn't what I wanted either. I actually worked out a way to tell me when the node goes "hot" (i.e., gets underlined).
Here's my subclass procedure code. I'll also post the structures under it in case anyone else wants to do this. I use the ComCtl32 method of subclassing, and I'll leave others on their own to figure that part out. I do save two longs (pointers to the form object and to the treeview object). If people need some code for that piece, let me know and I'll try and help out. For one of the longs, I just use the subclassing dwRefData variable, but I still need one more and I used a little "trick" to store that.
Code:
Private Function TreeViewHotEvent_Proc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
If uMsg = WM_DESTROY Then
UnSubclassSomeWindow hWnd, AddressOf_TreeViewHotEvent_Proc, uIdSubclass
TreeViewHotEvent_Proc = NextSubclassProcOnChain(hWnd, uMsg, wParam, lParam)
Exit Function
End If
If IdeStopButtonClicked Then ' Protect the IDE. Don't execute any specific stuff if we're stopping. We may run into COM objects or other variables that no longer exist.
TreeViewHotEvent_Proc = NextSubclassProcOnChain(hWnd, uMsg, wParam, lParam)
Exit Function
End If
'
Dim tvw As TreeView
Dim frm As VB.Form
'
Const CDIS_HOT As Long = &H40&
Const CDIS_NEARHOT As Long = &H400&
'Const TVN_GETDISPINFOA As Long = -403&
'Const TVN_GETINFOTIPA As Long = -413&
Const NM_CUSTOMDRAW As Long = -12&
Const OCM_NOTIFY As Long = 8270&
'Const WM_NOTIFY As Long = 78&
Const WM_MOUSELEAVE As Long = 675&
Dim uNMHDR As NMHDR
'Dim uTVDISPINFOA As TVDISPINFOA
'Dim uNMTREEVIEW As NMTREEVIEW
Dim uNMTVCUSTOMDRAW As NMTVCUSTOMDRAW
Dim uNMCUSTOMDRAWINFO As NMCUSTOMDRAWINFO
'
'Debug.Print WindowsMessage(uMsg)
'
Select Case uMsg
Case WM_MOUSELEAVE
GoSub NotHot
Case OCM_NOTIFY
CopyMemory uNMHDR, ByVal lParam, LenB(uNMHDR)
Select Case uNMHDR.code
'Case TVN_GETDISPINFOA
' CopyMemory uTVDISPINFOA, ByVal lParam, LenB(uTVDISPINFOA)
'Case TVN_GETINFOTIPA
' CopyMemory uNMTREEVIEW, ByVal lParam, LenB(uNMTREEVIEW)
Case NM_CUSTOMDRAW
CopyMemory uNMTVCUSTOMDRAW, ByVal lParam, LenB(uNMTVCUSTOMDRAW)
CopyMemory uNMCUSTOMDRAWINFO, ByVal uNMTVCUSTOMDRAW.nmcd, LenB(uNMCUSTOMDRAWINFO)
If uNMCUSTOMDRAWINFO.uItemState And (CDIS_HOT Or CDIS_NEARHOT) Then
'Debug.Print uNMCUSTOMDRAWINFO.dwItemSpec ' <--- that's the hItem of the "hot" or "near hot" node.
Set tvw = TreeviewObjectFromPtr(dwRefData)
dwRefData = GetExtraData(hWnd, ID_ForTvwHot)
Set frm = FormObjectFromPtr(dwRefData)
On Error Resume Next
Call frm.TvwNodeHot(frm, tvw, uNMCUSTOMDRAWINFO.dwItemSpec) ' MUST be public, or we can't find it this way.
On Error GoTo 0
End If
End Select
End Select
TreeViewHotEvent_Proc = NextSubclassProcOnChain(hWnd, uMsg, wParam, lParam)
Exit Function
'
NotHot:
Set tvw = TreeviewObjectFromPtr(dwRefData)
dwRefData = GetExtraData(hWnd, ID_ForTvwHot)
Set frm = FormObjectFromPtr(dwRefData)
On Error Resume Next
Call frm.TvwNodeHot(frm, tvw, 0&) ' MUST be public, or we can't find it this way.
On Error GoTo 0
Return
End Function
And the structures:
Code:
Private Type NMHDR
hwndFrom As Long
idFrom As Long
code As Long
End Type
'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
'Private Type TVDISPINFOA
' hdr As NMHDR
' item As TVITEM
'End Type
'Private Type NMTREEVIEW
' hdr As NMHDR
' Action As Long
' itemOld As TVITEM
' itemNew As TVITEM
' ptDrag As POINTAPI
'End Type
Private Type NMCUSTOMDRAWINFO
hdr As NMHDR
dwDrawStage As Long
hdc As Long
rc As RECT
dwItemSpec As Long
uItemState As Long
lItemlParam As Long
End Type
Private Type NMTVCUSTOMDRAW
nmcd As NMCUSTOMDRAWINFO
clrText As Long
clrTextBk As Long
iLevel As Long
End Type
As far as I'm concerned, this one is resolved. Again, thanks Fafalone for getting me pointed in the correct direction.
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.
Btw, OCM_NOTIFY depends on parent hwnd reflecting WM_NOTIFY.
VB.Form does it but there could be a controls container that does not (e.g. VB.UserControl or PictureBox), so placing a TreeView control in a custom UserControl container (or a PictureBox) might not behave as expected. Do test!
NOTE: I've deleted the attachment, as DaveDavis found a bug, and that bug is now fixed. See post #20 for the latest (bug fixed). The write-up here (including wqweto's caveat) still applies though.
Originally Posted by Elroy
Ok, when you hover (just mouse over) items in a TreeView, they underline the item you're hovering.
I'd like an event that'd tell me when that's happening, and preferably another event (or the same one with a flag) that tells me when it's no longer happening.
Ok, I quoted myself to remind people of what I wanted. I didn't "exactly" get an event that tells me when a node is no longer "hot", but I did manage to get the event to tell me when the mouse is no longer over the control, which was good enough for my needs.
Also, just an FYI, that "hovering" is called going "HOT", which I learned.
DaveDavis, just take a look at the TvwNodeHot event in Form1 and trace back from there to see what I did. The TreeView is subclassed so be a bit careful. So long as you're not tracing through code, the "Stop" button should be fine in this project though. You've got my full-blown method for subclassing with the ComCtl32.dll.
Just as an FYI, the subclassing for the TreeView is completely encapsulated, so you can do this for multiple TreeViews on a form, or for TreeViews on multiple forms, or any combination thereof.
Example attached.
EDIT: That looks like a good caveat from Wqweto. My TreeView is directly on the form so it's not a problem for me. However, I didn't test with a TreeView in a frame (or other container). I'll leave that for others.
Last edited by Elroy; Jan 12th, 2022 at 10:29 AM.
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.
p.s. If anyone has ideas on how to make the "no longer hot" work better, I'm listening. I'm just using WM_MOUSELEAVE, which sort of works, but it'd be better if I found something that told me the instant that the node was no longer underlined. I haven't figured that one out so far.
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.
I've deleted this post. It was a change, but I wasn't happy with it. Also, it had the bug that DaveDavis found. So, I just deleted it.
Last edited by Elroy; Jan 12th, 2022 at 10:27 AM.
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.
Ok, I quoted myself to remind people of what I wanted. I didn't "exactly" get an event that tells me when a node is no longer "hot", but I did manage to get the event to tell me when the mouse is no longer over the control, which was good enough for my needs.
Also, just an FYI, that "hovering" is called going "HOT", which I learned.
DaveDavis, just take a look at the TvwNodeHot event in Form1 and trace back from there to see what I did. The TreeView is subclassed so be a bit careful. So long as you're not tracing through code, the "Stop" button should be fine in this project though. You've got my full-blown method for subclassing with the ComCtl32.dll.
Just as an FYI, the subclassing for the TreeView is completely encapsulated, so you can do this for multiple TreeViews on a form, or for TreeViews on multiple forms, or any combination thereof.
Example attached.
EDIT: That looks like a good caveat from Wqweto. My TreeView is directly on the form so it's not a problem for me. However, I didn't test with a TreeView in a frame (or other container). I'll leave that for others.
After compiled, the demo can't run. Please check what is going on.
After compiled, the demo can't run. Please check what is going on.
Well that's really curious. I didn't think to compile it, not imagining that it'd be any different. I don't have time right now, but I'll figure it out in the next couple of days.
EDIT: I took a quick look at the CopyMemory statements, and didn't see anything obvious. It's going to take some debugging to figure out where it's crashing.
Last edited by Elroy; Jan 11th, 2022 at 07:51 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.
Ok, I've got the problem figured out. I was doing one too many CopyMemory calls, thinking I had a pointer to a structure, when I actually already had the whole structure.
It's all fixed now. Also, to figure it out, I used my persistent debug window (which works with compiled exexutables) found here. If you wish to use that, just compile it (or not) and run it, leaving that persistent debug window open while you're running this little test program. Also, that little persistent debug window sometimes gets a false positive as a virus, I guess because it's doing inter-program listening (but look at the code, there's nothing harmful in it).
But anyway, this program is now fixed and working as I intended (compiled or not).
Here's the new event:
Code:
Public Sub TvwNodeHot(oForm As Form, oTreeview As TreeView, hItem As Long)
' Custom event from subclassing.
'
If hItem = 0& Then
Debug.Print "Item no longer 'hot' as mouse is off of treeview."
DebugPrint "Item no longer 'hot' as mouse is off of treeview."
Else
Dim oNode As Node
Set oNode = GetTreeNodeFromHandle(oTreeview, hItem)
Debug.Print "Item " & oNode.Text & " went 'hot'."
DebugPrint "Item " & oNode.Text & " went 'hot'."
End If
End Sub
I did the debug both ways so you can still run it from the IDE without the persistent debug window.
Latest (with fixes) attached.
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.
Ok, I've got the problem figured out. I was doing one too many CopyMemory calls, thinking I had a pointer to a structure, when I actually already had the whole structure.
It's all fixed now. Also, to figure it out, I used my persistent debug window (which works with compiled exexutables) found here. If you wish to use that, just compile it (or not) and run it, leaving that persistent debug window open while you're running this little test program. Also, that little persistent debug window sometimes gets a false positive as a virus, I guess because it's doing inter-program listening (but look at the code, there's nothing harmful in it).
But anyway, this program is now fixed and working as I intended (compiled or not).
Here's the new event:
Code:
Public Sub TvwNodeHot(oForm As Form, oTreeview As TreeView, hItem As Long)
' Custom event from subclassing.
'
If hItem = 0& Then
Debug.Print "Item no longer 'hot' as mouse is off of treeview."
DebugPrint "Item no longer 'hot' as mouse is off of treeview."
Else
Dim oNode As Node
Set oNode = GetTreeNodeFromHandle(oTreeview, hItem)
Debug.Print "Item " & oNode.Text & " went 'hot'."
DebugPrint "Item " & oNode.Text & " went 'hot'."
End If
End Sub
I did the debug both ways so you can still run it from the IDE without the persistent debug window.
Latest (with fixes) attached.
There're two "Persistent Debug Message" window popup.
There're two "Persistent Debug Message" window popup.
If you're not running my Persistent Debug Window program, then just comment out those two lines of code. These:
Code:
DebugPrint "Item no longer 'hot' as mouse is off of treeview."
Code:
DebugPrint "Item " & oNode.Text & " went 'hot'."
It's the lines with no period between "Debug" and "Print".
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.
Can the same technique of "detecting hovering/underlining" apply to ListView SubItem?
In some ways, the ListView isn't as complex as the TreeView. Also, I tend to almost always use the ListView in "Report" mode. As such, there's no hierarchy to it.
When used like this, I have the following procedure for getting what I need for the ListView.
Code:
Public Sub ListViewItemAndSubHitTest(lvw As ListView, X As Single, Y As Single, LvwHitInfo As LVHITTESTINFO)
' x and y are in twips. Convert them to pixels for the API call.
' The LvwHitInfo is actually returned with the lFlags, lItem, & lSubItem filled out.
LvwHitInfo.lFlags = 0
LvwHitInfo.lItem = 0
LvwHitInfo.lSubItem = 0
LvwHitInfo.pt.X = X / TwipsPerPel
LvwHitInfo.pt.Y = Y / TwipsPerPel
' Fill in UDT, and return.
SendMessageA lvw.hWnd, LVM_SUBITEMHITTEST, 0, LvwHitInfo
LvwHitInfo.lItem = LvwHitInfo.lItem + 1 ' In VB, the items are 1 based and in the API they are 0 based.
End Sub
All the support procedures aren't there, but you should be able to figure it out. Here's the UDT.
Code:
Public Type LVHITTESTINFO
pt As POINTAPI
lFlags As Long
lItem As Long
lSubItem As Long
End Type
Also, you might be better using something like the following, rather than that TwipsPerPel stuff:
Code:
Dim pt32 As POINTAPI
'
GetCursorPos pt32
ScreenToClient lvw.hWnd, pt32
... and then use pt32 to set LvwHitInfo.pt. Using that approach, you won't even need to pass X and Y into ListViewItemAndSubHitTest.
EDIT: Here, I only see three API calls. Here they are:
Code:
Public Declare Function SendMessageA Lib "user32" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByRef lParam As Any) As Long
Public Declare Function ScreenToClient Lib "user32" (ByVal hWnd As Long, lpPoint As POINTAPI) As Long
Public Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
EDIT2: Crud, ok, here's the PointAPI definition:
Code:
Public Type POINTAPI
x As Long
y As Long
End Type
Last edited by Elroy; Jan 12th, 2022 at 07:53 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.
You'll have to put all of that into the ListView's MouseMove event (similar to what Fafalone suggested above), so it's not quite as good as subclassing and making an explicit event. But it's all I've got right now. I'm sure you could subclass the ListView and do a more specific HitTest event, but I've never done it.
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.
In some ways, the ListView isn't as complex as the TreeView. [...]
Got a good laugh out of that one. Sorry lol
The ListView control has extreme complexity. There's more undocumented features than documented ones (e.g. footers, subitem controls), and just with the documented ones you've got virtual mode, group view... not to mention... Report mode is what you want if you want to make a hierarchy in a ListView:
As to DaveDavis' question, yup you can use the same custom draw method to detect ListView subitems. We did that a while back to make a demo that highlights subitems on mouseover:
haha, yeah, I didn't say it wasn't complex. And yeah, I've already got subclassing procedures for the ListView, just not one that does precisely what DaveDavis wanted. But I'm quite sure it's possible. I opened another thread about message pump constants, and the ListView has a ton of them just for its own messages.
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.
If you're doing a message lookup for the ListView, here's the list I compiled. There's a lot of undocumented messages that you won't find in most lists.
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.