You can use FindWindowEx to locate the child window contained within the listview and then use GetClientRect of that child to get the overall dimensions:
Version 5 of the control has a child with classname of "SysHeader32"
Version 6 of the control has a child with classname of "msvb_lib_header"
Note that the listview's header is another child window inside the listview
The listitem object has a Height property. That could be helpful. You can divide the client height by the listitem height to get a feel for how many listitems can fit without being clipped. Of course this assumes you have a listitem in the listview. This can't really be used to calculate the height of a listview because you can have 100s of list items; where only a few are displayed
edited: oops, got my brain-wires crossed. The listview doesn't have a separate child window for the view itself, but it does have one for any displayed column headers. Fixed the header class names above.
Last edited by LaVolpe; Mar 30th, 2019 at 08:57 PM.
Insomnia is just a byproduct of, "It can't be done"
Thank you for that info. Done some Googling for FindWindowEx and it looks pretty advanced stuff (for me).
Maybe there's some example code? What I hope to do do add x items, then calculate the height of the listview control.
(Using this height to set a vertical scrollbar on a Frame)
Private Declare Function GetClientRect Lib "user32.dll" (ByVal hWnd As Long, ByRef lpRect As RECT) As Long
Private Declare Function FindWindowEx Lib "user32.dll" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
and here is a sample call. Note: for version 5 of the control, use "SysHeader32" in the FindWindowEx call below
Code:
Dim wRect As RECT, hdrRect As RECT
Dim cy As Long, cHwnd As Long
GetClientRect ListView1.hWnd, wRect
cHwnd = FindWindowEx(ListView1.hWnd, 0, "msvb_lib_header", vbNullString)
If cHwnd <> 0 Then GetClientRect cHwnd, hdrRect
cy = (wRect.Bottom - wRect.Top) - (hdrRect.Bottom - hdrRect.Top) + 1
cy will be the internal height, excluding borders and the header bar
If you don't want to exclude the header bar in the height calculation, simply don't call the 2nd GetClientRect statement
Insomnia is just a byproduct of, "It can't be done"
The code is running without any errors but isn't giving the expected result. cy is returning 163 with 9 entries.
If I exclude the header this value changes to 180
Both are too small. The height should be about 2500.
Thanks Steve I see now the expected result, and find it's not what I want. Sorry La vople if I asked for the wrong thing.
If I set Listview height to 500, add eight items, Listview height remains 500 (LaVolpe's code returns 420).
What I actually want if the height the Listview would be to display the 8 items. Sort of an AutoSize value.
Maybe it's not possible... so perhaps Choose and height preset per x items.
"hWnd" is something I've never figured out. Seen it mentioned of course, but never much more. Same as APIs.
You are asking for something like Integral Height. Want whole items displayed, not partial items?
Here are the API declarations I used. Not the same as in post #4
Code:
Private Declare Function GetClientRect Lib "user32.dll" (ByVal hWnd As Long, ByRef lpRect As RECT) As Long
Private Declare Function GetWindowRect Lib "user32.dll" (ByVal hWnd As Long, ByRef lpRect As RECT) As Long
Private Declare Function FindWindowEx Lib "user32.dll" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Here is the code you can place in a routine of yours. Heavily commented so you can follow along
Code:
Dim cyItem As Single, cyHdr As Long
Dim cWnd As Long, wRect As RECT
Dim cyOffset As Long, cyScale As Single
If ListView1.ListItems.Count = 0 Then Exit Sub ' need at least 1 list item
' get height of 1 list item
' note: appears to return size in container's scalemode, both v5 & v6
cyItem = ListView1.ListItems(1).Height
' APIs below will return sizes in pixels
' get height of the header bar
cWnd = FindWindowEx(ListView1.hWnd, 0, "msvb_lib_header", vbNullString)
If cWnd <> 0 Then
GetWindowRect cWnd, wRect
cyHdr = wRect.Bottom - wRect.Top
End If
' get height of the listview's client area (inside the borders)
GetClientRect ListView1.hWnd, wRect
cyOffset = wRect.Bottom - wRect.Top
' get height of the entire listview, as it is now and its scale relative to its parent's scalemode
GetWindowRect ListView1.hWnd, wRect
cyScale = ListView1.Height / (wRect.Bottom - wRect.Top)
' calculate difference between inside/outside. This is the border size of top/bottom borders
cyOffset = (wRect.Bottom - wRect.Top - cyOffset)
' now calculate the size needed to display 8 listitems in container's scalemode
cyItem = 8 * Abs(cyItem) + (cyHdr + cyOffset) * cyScale
' note: Abs() used in case scalemode is custom, i.e., having a negative scaleheight
ListView1.Height = cyItem
Edited: 3 tweaks may be needed.
1. Obviously substitute ListView1 with your listview's actual name
2. If placing code in a function, replace Exit Sub with Exit Function
3. If using v5 of common controls use "SysHeader32" vs "msvb_lib_header" in FindWindowEx
FYI. I hard-coded the nr items to size to 8 per your previous posting. You may want to pass the number of rows as a parameter to your routine instead. That way you can set the integral height to whatever nr of rows you want at any time. If so, replace that hard-coded 8 (2nd to last line) with your parameter name
Last but not least... The above code will work. But I personally wouldn't use it. Why? I dislike any control option that uses Integral Height (sizes itself based on content) during runtime. Reason is that, in this case, depending on the Font used in the control, then the routine would change the control's height -- potentially much larger. What if you had a checkbox or button or some other control just below the listview? Now they would overlap unless you also adjusted all the other controls around the listview so no overlapping occurred. To each their own. If it's a must-have, then be aware that you may need to move other controls after you calculated the height of your listview. If the new size extended below your form, you'll need to adjust the height of your form. Though this problem could be far more relative in an application that is DPI aware or allowing users to choose their own font
Last edited by LaVolpe; Mar 31st, 2019 at 09:37 PM.
Insomnia is just a byproduct of, "It can't be done"
My goal is to have the listview (and other controls) on a Frame. Their integral height sets the Top position for each.
And the Frame scrolls the lot. The LV font is MS Sans Serif , Regular 8.
If I have 8 items cyitem = 1963.11 and the listview acquires a vertical scrollbar.
I spent many hours playing around and then a penny dropped with cyItem = ListView1.ListItems(1).Height
If this was multiplied by LV count, what would happen. I added a coloured label and added this after yours
Code:
cyItem = 8 * Abs(cyItem) + (cyHdr + cyOffset) * cyScale
' note: Abs() used in case scalemode is custom, i.e., having a negative scaleheight
Debug.Print cyItem
cyItem = ListView1.ListItems(1).Height * (ListView1.ListItems.Count + 2)
Debug.Print cyItem
Debug.Print ListView1.ListItems.Count
ListView1.Height = cyItem
Label1.Left = ListView1.Left
Label1.Top = cyItem + ListView1.Top
1963.11
2097.638
8
I can't figure why the +2 is needed. Maybe the Header... puzzling. The original cyItem value is about 134 less than the other, regardless of Items count.
I'm probably missing something.. anyway I find the LV control isn't behaving very well. Can you detect a click on other than column 1, do you know ?
A problem arises when the row clicked sometimes isn't right, instead col 1 is selected on another (the previous?) row.
In my VBCCR ListView I have a public method for the users called 'ComputeControlSize'.
Below is the code. You need to adapt some few things for your MS ListView. Should be worth a try.
Code:
Public Sub ComputeControlSize(ByVal VisibleCount As Long, ByRef Width As Single, ByRef Height As Single, Optional ByVal ProposedWidth As Single, Optional ByVal ProposedHeight As Single)
Attribute ComputeControlSize.VB_Description = "A method that returns the width and height for a given number of visible list items."
If VisibleCount < 0 Then Err.Raise 380
If ListViewHandle <> 0 Then
Dim RetVal As Long, RC(0 To 1) As RECT, ProposedX As Long, ProposedY As Long
GetWindowRect ListViewHandle, RC(0)
GetClientRect ListViewHandle, RC(1)
With UserControl
If ProposedWidth <> 0 Then
ProposedX = CLng(.ScaleX(ProposedWidth, vbContainerSize, vbPixels))
Else
ProposedX = -1
End If
If ProposedHeight <> 0 Then
ProposedY = CLng(.ScaleY(ProposedHeight, vbContainerSize, vbPixels))
Else
ProposedY = -1
End If
RetVal = SendMessage(ListViewHandle, LVM_APPROXIMATEVIEWRECT, IIf(PropView = LvwViewReport, VisibleCount - 1, VisibleCount), MakeDWord(ProposedX, ProposedY))
If LoWord(RetVal) <> 0 Then Width = .ScaleX(LoWord(RetVal) + ((RC(0).Right - RC(0).Left) - (RC(1).Right - RC(1).Left)), vbPixels, vbContainerSize)
If HiWord(RetVal) <> 0 Then Height = .ScaleY(HiWord(RetVal) + ((RC(0).Bottom - RC(0).Top) - (RC(1).Bottom - RC(1).Top)), vbPixels, vbContainerSize)
End With
End If
End Sub
In the first param VisibleCount you specify what count of visible items you desire.
The second and third are returning the resulti g width and height.
@Alexander. What did not work in post #9? The listview in my tests was sized perfectly. My tests included both versions of the control, different font sizes, different visible rows, and different scalemodes.
Insomnia is just a byproduct of, "It can't be done"
I'm still trying to figure it all out. I think now you're right except I didn't expect a scroll bar.
Adding 134 to cyitem and setting that height it goes away. But with and without the 134 cyitem is correct
and the label displays where it should. It's only 8 items shouldn't have a scrollbar. I'm confused. I'll attach what I have, suspecting all is correct, except for me!
I'm still trying to figure it all out. I think now you're right except I didn't expect a scroll bar.
So, to better describe the scenario. When the LV has exactly 8 items (or whatever number the LV is sized to), you don't want the scrollbar? Understandable.
Since that wasn't explained, my routine in post #9 is not guaranteed to do that. Why? Well, the LV is probably using a pixel around the header (2 total), then another between each listitem (7 more). That equates to 9 pixels or 135 twips. The 135 twips can be hardcoded but only if it behaves that way every time, regardless of font, DPI, etc. Otherwise, more calculations are needed in order to discover those offsets.
Insomnia is just a byproduct of, "It can't be done"
Looks like either use cyitem result or the .ListItems(1).Height * count are a solution. Cool, thank you.
I don't suppose you know a way to detect a sub item click ? Or change the column which accept click event ? Seems to be only the first one.