Re: border a single item on mousmove event in listview
Trying to ask the same question a different way? I recall you asking how to custom highlight a listview subitem.
Actually drawing the border will likely require subclassing. It is possible to create a 'fake' border-control and position if over the listview subitems. Though I think that is not a great solution. The steps for that are briefly outlined here. APIs to use for cutting holes in forms: SetWindowRgn along with region-creating APIs
- use a borderless form
- cut a rectangular hole in the form leaving outside edges (2+ pixels) visible. This will be the border-control.
- set that form's backcolor to the border color you want to use
- as mouse moves over subitems or entire item if that's what you want then
:: get coordinates of the subitem and its size & convert them to screen coordinates
:: resize the border-control form to the subitem, adjusting for the border width/height
:: make that form visible and move it so it's positioned over the listview
- when the border-control form is no longer needed, either unload it or hide it
I'll leave the above as an exercise for the curious
Last edited by LaVolpe; Jul 28th, 2020 at 11:56 AM.
Insomnia is just a byproduct of, "It can't be done"
Re: border a single item on mousmove event in listview
It's really not that hard to subclass and do custom colors. Here's an old demo by Brad Martinez:
I'd recommend a newer subclassing method, e.g. the one in my sig, all you need to worry about is the response to the NM_CUSTOMDRAW message:
Code:
Case NM_CUSTOMDRAW
Static iElement As Long
'Debug.Print "&H" & Hex(lvcd.nmcd.dwDrawStage)
Select Case lvcd.nmcd.dwDrawStage
' ====================================================
Case CDDS_PREPAINT
' Tell the listview we want CDDS_ITEMPREPAINT for each item
WndProc = CDRF_NOTIFYITEMDRAW
Exit Function
' ====================================================
Case CDDS_ITEMPREPAINT
If g_fNewDraw Then
iElement = lvcd.nmcd.dwItemSpec And &HF
Else
iElement = ((lvcd.nmcd.dwItemSpec * 4) - 1) And &HF
End If
Call SelectObject(lvcd.nmcd.hdc, g_IFonts(iElement).hFont)
lvcd.clrText = g_crl16(iElement)
lvcd.clrTextBk = g_crl16(15 - iElement)
MoveMemory ByVal lParam, lvcd, Len(lvcd)
' Tell the listview we want (CDDS_ITEMPREPAINT Or CDDS_SUBITEM)
' for each item's subitems, and that we changed the item's font.
WndProc = CDRF_NOTIFYSUBITEMDRAW Or CDRF_NEWFONT
Exit Function
' ====================================================
Case (CDDS_ITEMPREPAINT Or CDDS_SUBITEM)
If g_fNewDraw Then
iElement = (lvcd.nmcd.dwItemSpec + (lvcd.iSubItem + 1)) And &HF
Else
iElement = (((lvcd.nmcd.dwItemSpec * 4) - 1) + (lvcd.iSubItem + 1)) And &HF
End If
'Debug.Print iElement; g_IFonts(iElement).Name
Call SelectObject(lvcd.nmcd.hdc, g_IFonts(iElement).hFont)
lvcd.clrText = g_crl16(iElement)
lvcd.clrTextBk = g_crl16(15 - iElement)
MoveMemory ByVal lParam, lvcd, Len(lvcd)
' Tell the listview that we changed the subitem's font.
WndProc = CDRF_NEWFONT
Exit Function
End Select ' lvcd.nmcd.dwDrawStage
#End If ' (WIN32_IE >= &H300)
End Select ' nmh.code
The message will be fired when the item gets a mouseover... so you'd add a block to get the cursor get the cursor position and do a hittest:
Code:
Private Function ListView_HitTestEx(hwndLV As Long, pInfo As LVHITTESTINFO) As Long
'HitTestEx is used if you need the iGroup and iSubItem members filled
ListView_HitTestEx = SendMessage(hwndLV, LVM_HITTEST, -1, pInfo)
End Function
Then you check if it's on an item, If (pInfo.Flags And LVHT_ONITEM) Then, and then get the item number from pInfo.iItem.
Re: border a single item on mousmove event in listview
Actually here, I was bored.
This project does highlighting both on click and on mouseover. On mouseover is a little flickery, I tried to minimize it by redrawing only the last and current item, so it's not that bad.
There's an option to change the text color too if you want.
Just drawing a border is a whole other ballgame, since there's no built in way, you'd have to do something even more complicated than highlighting. But since you've been asking about highlighting, and this will at least show you how to identify the item, figured it would be useful.
This uses the Common Controls 5.0 ListView, but I've confirmed it also works with the 6.0 control, and would of course work with an API control.
Here's the key routines, as mentioned above, it's just a small block of code to respond to NM_CUSTOMDRAW:
Code:
Public Sub SetOnMouseMove(px As Long, py As Long)
If gOnMO = True Then
Dim nOld As Long
nOld = gHighlight
Dim LVHTI As LVHITTESTINFO
LVHTI.pt.x = px
LVHTI.pt.y = py
SendMessage Form1.ListView1.hWnd, LVM_SUBITEMHITTEST, 0&, LVHTI
If (LVHTI.Flags And LVHT_ONITEM) Then
gHighlight = LVHTI.iItem
gHighlightSub = LVHTI.iSubitem
Else
gHighlight = -1: gHighlightSub = -1
End If
If nOld <> -1 Then
SendMessage Form1.ListView1.hWnd, LVM_REDRAWITEMS, nOld, ByVal nOld
End If
If gHighlight <> -1 Then
SendMessage Form1.ListView1.hWnd, LVM_REDRAWITEMS, gHighlight, ByVal gHighlight
End If
End If
End Sub
Public Function LVWndProc(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
Select Case uMsg
Case WM_NOTIFY
Dim tNMH As NMHDR
CopyMemory tNMH, ByVal lParam, Len(tNMH)
Select Case tNMH.Code
Case NM_CLICK
If gOnMO = False Then
Dim LVHTI As LVHITTESTINFO
Dim nmia As NMITEMACTIVATE
CopyMemory nmia, ByVal lParam, LenB(nmia)
LVHTI.pt.x = nmia.PTAction.x
LVHTI.pt.y = nmia.PTAction.y
SendMessage Form1.ListView1.hWnd, LVM_SUBITEMHITTEST, 0&, LVHTI
If (LVHTI.Flags And LVHT_ONITEM) Then
gLastItem = gHighlight
gHighlight = LVHTI.iItem
gHighlightSub = nmia.iSubitem
Else
gHighlight = -1: gHighlightSub = -1
End If
RedrawList Form1.ListView1.hWnd
End If
Case NM_CUSTOMDRAW
Dim nmcdr As NMLVCUSTOMDRAW
CopyMemory nmcdr, ByVal lParam, LenB(nmcdr)
Select Case nmcdr.nmcd.dwDrawStage
Case CDDS_PREPAINT
LVWndProc = CDRF_NOTIFYITEMDRAW
Exit Function
Case CDDS_ITEMPREPAINT
LVWndProc = CDRF_NOTIFYSUBITEMDRAW Or CDRF_NEWFONT
Exit Function
Case (CDDS_ITEMPREPAINT Or CDDS_SUBITEM)
If gHighlightSub > 0 Then
If (nmcdr.nmcd.dwItemSpec = gHighlight) And (nmcdr.iSubitem = gHighlightSub) Then
If gSetBk Then nmcdr.clrTextBk = vbYellow
If gSetTxt Then nmcdr.clrText = vbRed
Else
If gSetBk Then nmcdr.clrTextBk = vbWhite
If gSetTxt Then nmcdr.clrText = vbBlack
End If
CopyMemory ByVal lParam, nmcdr, LenB(nmcdr)
LVWndProc = CDRF_NEWFONT
Exit Function
End If
End Select
End Select
Case WM_DESTROY
Call UnSubclass(hWnd, PtrLVWndProc)
End Select
LVWndProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
End Function
Last edited by fafalone; Jul 28th, 2020 at 08:21 PM.
Reason: Cleaned up code in demo project a bit
Re: border a single item on mousmove event in listview
FYI to fafalone. The border is more tricky because you need to also trap the CDDS_ITEMPOSTPAINT event else any custom drawing during CDDS_ITEMPREPAINT will get erased.
Insomnia is just a byproduct of, "It can't be done"
Re: border a single item on mousmove event in listview
I'm playing around with the project from post #5 and I can move my border around the list view on the sub items but it is not exactly what I had in mind. What I don't know is how to capture the X and Y of the sub items that the mouse is over. I have my single line of code in this Sub
Code:
Public Sub SetOnMouseMove(px As Long, py As Long)
If gOnMO = True Then
Dim nOld As Long
nOld = gHighlight
Dim LVHTI As LVHITTESTINFO
LVHTI.pt.x = px
LVHTI.pt.y = py
SendMessage Form1.ListView1.hWnd, LVM_SUBITEMHITTEST, 0&, LVHTI
If (LVHTI.Flags And LVHT_ONITEM) Then
gHighlight = LVHTI.iItem
gHighlightSub = LVHTI.iSubitem
Form1.UC_Border.Move px * 15, (py + 20) * 15
Else
gHighlight = -1: gHighlightSub = -1
End If
If nOld <> -1 Then
SendMessage Form1.ListView1.hWnd, LVM_REDRAWITEMS, nOld, ByVal nOld
End If
If gHighlight <> -1 Then
SendMessage Form1.ListView1.hWnd, LVM_REDRAWITEMS, gHighlight, ByVal gHighlight
End If
End If
End Sub
Also I don't know how to use CDDS_ITEMPOSTPAINT. I have it in the LVWndProc function like this but it is never entered so I know I'm not using it correctly and even if I did know I wouldn't know what code should be there
Code:
'
'
Public Function LVWndProc(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
Select Case uMsg
Case WM_NOTIFY
Dim tNMH As NMHDR
CopyMemory tNMH, ByVal lParam, Len(tNMH)
Select Case tNMH.Code
Case NM_CLICK
If gOnMO = False Then
Dim LVHTI As LVHITTESTINFO
Dim nmia As NMITEMACTIVATE
CopyMemory nmia, ByVal lParam, LenB(nmia)
LVHTI.pt.x = nmia.PTAction.x
LVHTI.pt.y = nmia.PTAction.y
SendMessage Form1.ListView1.hWnd, LVM_SUBITEMHITTEST, 0&, LVHTI
If (LVHTI.Flags And LVHT_ONITEM) Then
gLastItem = gHighlight
gHighlight = LVHTI.iItem
gHighlightSub = nmia.iSubitem
Else
gHighlight = -1: gHighlightSub = -1
End If
RedrawList Form1.ListView1.hWnd
End If
Case NM_CUSTOMDRAW
Dim nmcdr As NMLVCUSTOMDRAW
CopyMemory nmcdr, ByVal lParam, LenB(nmcdr)
Select Case nmcdr.nmcd.dwDrawStage
Case CDDS_ITEMPOSTPAINT
'
'
'
Exit Function
Case CDDS_PREPAINT
LVWndProc = CDRF_NOTIFYITEMDRAW
Exit Function
Case CDDS_ITEMPREPAINT
LVWndProc = CDRF_NOTIFYSUBITEMDRAW Or CDRF_NEWFONT
Exit Function
Case (CDDS_ITEMPREPAINT Or CDDS_SUBITEM)
If gHighlightSub > 0 Then
If (nmcdr.nmcd.dwItemSpec = gHighlight) And (nmcdr.iSubitem = gHighlightSub) Then
If gSetBk Then nmcdr.clrTextBk = vbYellow
If gSetTxt Then nmcdr.clrText = vbRed
Else
If gSetBk Then nmcdr.clrTextBk = vbWhite
If gSetTxt Then nmcdr.clrText = vbBlack
End If
CopyMemory ByVal lParam, nmcdr, LenB(nmcdr)
LVWndProc = CDRF_NEWFONT
Exit Function
End If
End Select
End Select
Case WM_DESTROY
Call UnSubclass(hWnd, PtrLVWndProc)
End Select
LVWndProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
End Function
Re: border a single item on mousmove event in listview
Originally Posted by Ordinary Guy
Also I don't know how to use CDDS_ITEMPOSTPAINT. I have it in the LVWndProc function like this but it is never entered so I know I'm not using it correctly and even if I did know I wouldn't know what code should be there
If you have further questions regarding handling custom drawing of the listview, you probably should start a new thread vs. unintentionally hijacking this thread for that purpose.
To draw during post-paint, trap this among your Case statements
Code:
Const CDDS_POSTPAINT As Long = &H2
Const CDDS_ITEM As Long = &H10000
Const CDDS_ITEMPOSTPAINT As Long = (CDDS_ITEM Or CDDS_POSTPAINT)
...
Case CDDS_ITEMPOSTPAINT
-- use the nmcdr.nmcd members for drawing, i.e., the .Hdc & .Rc members
Case (CDDS_ITEMPOSTPAINT Or CDDS_SUBITEM)
-- use the nmcdr.nmcd members for drawing, i.e., the .Hdc & .Rc members
To get the post-paint events/message, return CDRF_NOTIFYPOSTPAINT, for example using your code:
Code:
...
Case CDDS_ITEMPREPAINT
LVWndProc = CDRF_NOTIFYSUBITEMDRAW Or CDRF_NEWFONT Or CDRF_NOTIFYPOSTPAINT
Exit Function
Case (CDDS_ITEMPREPAINT Or CDDS_SUBITEM)
If gHighlightSub > 0 Then
...
LVWndProc = CDRF_NEWFONT Or CDRF_NOTIFYPOSTPAINT
Exit Function
End If
...
edited: forgot a constant
Const CDRF_NOTIFYPOSTPAINT As Long = &H10
Last edited by LaVolpe; Jul 30th, 2020 at 07:51 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: border a single item on mousmove event in listview
I got that nmcdr.nmcd.rc member and use it as nmcdr.nmcd.rc.Left and nmcdr.nmcd.rc.Top. Now the .Left comes very close to the left edge of the column but the .Top is like really wierd. .Top gives really large numbers like 1392577417 and 6106344 which are totally out of range so here I am completely baffeled
Re: border a single item on mousmove event in listview
Originally Posted by DaveDavis
It is harder to draw highlight background in text region only. I ever give up.
It's a bit more work but completely doable with subclassing. Here is an example, notice that I gave a 'raised' appearance to the gold highlighted subitem. The problem really is that you have so very little room to draw a border before you start to overdraw onto the item text.
Edited: If really wanting a border on a mouse move event, don't think I'd go the route taken in my sample image. That's a lot of listview redrawing and potential flicker to draw within a subitem. A smarter option may be to overlay a movable window onto the listivew or getting the listview hDC (GetDC, GetWindowDC APIs) and drawing an XOR (rubberband-like) border. In my sample, in actual code, the border and gold highlighting are applied on clicks and keyboard navigation, not mouse movements.
Last edited by LaVolpe; Aug 1st, 2020 at 11:35 AM.
Insomnia is just a byproduct of, "It can't be done"
Re: border a single item on mousmove event in listview
My last work, attached .
Have difficult to show a rectangle in item out the raw 10...
If i select a item, for example on raw 11, the rectangle appear out the listview.
Re: border a single item on mousmove event in listview
Originally Posted by luca90
My last work, attached .
Have difficult to show a rectangle in item out the raw 10...
If i select a item, for example on raw 11, the rectangle appear out the listview.
Code:
Dim itmTmp As ListItem
Set itmTmp = ListView1.GetFirstVisible
picMover1.Height = ALTEZZA
picMover1.Width = LARGHEZZA
picMover1.Left = SINISTRA + 30
picMover1.Top = SOTTO + 40 - (itmTmp.Index - 1) * ITMX.Height