How to Detect Listbox Vertical Scrollbar?

Is there a way to detect if a listbox’s vertical scrollbar is showing after being drawn?

I have two listboxes stacked vertically, the top most acting as column headers (so a single row), the lower holding results of a query as a data listbox. The data listbox could potentially have a vertical and horizontal scrollbar. I have both listbox’s horizontal scrollbars linked to scroll together. But if the data listbox has a vertical scrollbar, when scrolling horizontally, the column header listbox cumulative width is shorter than the data listbox’s cumulative width. So when scrolling to the far right in the data listbox, the column listbox’s last two columns aren’t visible and there’s a small white gap equivalent to the data listbox’s scrollbar width.

What I want to know if is there is a way to detect if the data listbox’s vertical scrollbar is showing, so I can add a buffer to calculate the header listbox’s scroll values?

I have code for this, I worked on it a while back, both horizontal and vertical detection, I even found a crash causing bug when developing it that will hopefully be fixed on the next release. I’m not at my computer for the rest of the day, I’ll post the code as soon as I get back.

VScrollBarIsShowing=((lb.rowheight*lb.listcount)>lb.height) AND lb.scrollbarvertical

not tested… but if the # of Rows * RowHeight exceeds the height of the box itself, then the scrollbar is showing

I have done it before but it was awhile back and don’t have the code with me so don’t recall which way I did way I did it…

But this should work

Dim VScrollBarVisble as Boolean If me.HasHeading then VScrollBarVisble = me.ListCount > 0 AND me.ColumnFromXY(me.Width-4, me.HeaderHeight + 3) = -1 Else VScrollBarVisble = me.ListCount > 0 AND me.ColumnFromXY(me.Width-4, 3) = -1 End if

[quote=332704:@Dave S] VScrollBarIsShowing=((lb.rowheight*lb.listcount)>lb.height) AND lb.scrollbarvertical

not tested… but if the # of Rows * RowHeight exceeds the height of the box itself, then the scrollbar is showing[/quote]

Dave that is the obvious way but has a gotcha…remember the autohide scrollbars option

The space available for rows (content area) depends on

  1. If it has a horizontal scrollbar (you would have to test if it is visible)
  2. If it has a header
  3. To be exact you also need to check if the listbox has a border
  • karen

I guess I did a workaround, which may be similar to what you’ve done. I was hoping for a quick check.

//Account for scrollbars
Dim theight As Integer = lbRH.ListCount * lbRH.RowHeight
	If theight > lbRH.Height Then
		lbCH.Width = lbCH.Width - 20
	End
Dim twidth As Integer
	For i As Integer = 0 To colwidths.Ubound
		twidth = twidth + colwidths(i).Val
	Next
	If twidth > lbCH.Width Then
		lbRH.Height = lbRH.Height - 20
	End

It gets the job done, but a simple boolean built into a listbox would be helpful.

When I was writing my listbox subclass having booleans to see if either scrollbar was visible would have simplified things!

BTW if you are going to do it you should be dynamically getting the columns widths if you allow the user to either resize the columns or resize the listbox with the window.

@Dave S I guess we had similar ideas.

@Karen Atkocius Thanks. I know I’m operating on specifics, so I know there are no headings (hence the additional listboxes), and the listboxes have equal borders. I’m also measuring the text in each header to draw the column width’s string, so I additionally can loop through that array to get the full width as well. I wasn’t familiar with the ColumnFromXY. Just for my understanding, you’re checking to make sure the mouse clicked inside of the listbox but not a cell?

you can get the column width from COLUMN(x).ActualWidth property

[quote=332709:@Jason Fink]@Dave S I guess we had similar ideas.

@Karen Atkocius Thanks. I know I’m operating on specifics, so I know there are no headings (hence the additional listboxes), and the listboxes have equal borders. I’m also measuring the text in each header to draw the column width’s string, so I additionally can loop through that array to get the full width as well. I wasn’t familiar with the ColumnFromXY. Just for my understanding, you’re checking to make sure the mouse clicked inside of the listbox but not a cell?[/quote]

Does not have to be in a cell just in the content area where a Cell COULD be . For example this still works if you have one column with a width of 100 and the listbox has width of 200…

But I would recommend you subclass the listbox and do this generically as a calculated property… That way you never have to worry about the logic again for other projects!

And as Dave pointed out you should be using COLUMN(x).ActualWidth… The listbox has a lot of subtleties and that will help make sure you don’t trip over them in edge cases.

  • karen

[quote=332708:@Karen Atkocius]When I was writing my listbox subclass having booleans to see if either scrollbar was visible would have simplified things!

BTW if you are going to do it you should be dynamically getting the columns widths if you allow the user to either resize the columns or resize the listbox with the window.[/quote]

I determine the column widths from preloaded text as an integer value (not percentage) and don’t allow them to be resized. While the window can be resized, I’m using Xojo’s IDE locks on the listbox elements to keep relative spacing from window edges to listboxes. With that, shouldn’t their attributes retain relativity to the window while the window is adjusted? The columns themselves don’t change in width, only the listbox itself, correct?

Thanks. - Jason

Yes…

But as I said I strongly suggest you do it generically in a subclass so it’s easy to reuse without having to worry about the details again.

  • Karen

BTW FWIW it annoys the heck out of me that Listbox. HeaderHeight does NOT return 0 if there is no heading!

When that was first introduced I tripped over that a few times before I remembered I also have to check listbox.HasHeading… We REALLY should not have to do that!

  • Karen

I was going to go make a feedback request because I like that idea, Karen.
But then I saw they’ve been ignoring you for 8 years: <https://xojo.com/issue/7693>

<https://xojo.com/issue/48183>

fwiw the heading height is a 1 liner now

dim actualHeight as integer = if(listbox.hasHeading, listbox.headingheight, 0)

(previously

 dim actualHeight as integer 
 if listbox.hasHeading then actualHeight = listbox.headingheight

[quote=332724:@Tim Parnell]I was going to go make a feedback request because I like that idea, Karen.
But then I saw they’ve been ignoring you for 8 years: <https://xojo.com/issue/7693>[/quote]

I forgot I did that… That was back when I was first writing my listbox subclass and trying to deal with all the possible permutations of listbox settings and content!

BTW Thanks Norm… that is one request I never got around to making after I solved it in my code… but maybe VerticalScrollbarWidth and HorizontalScrollbarHeight that return 0 when they are not present would be better… More future proof if UI’s change!

  • Karen

[quote=332705:@Karen Atkocius]But this should work
[/quote]

The previous code does not consider all cases. It assumes both that :
ScrollBarVertical Is true
And
AutoHideScrollbars = True

That means that code can produce a wrong result if AutoHideScrollBars is false and the listbox has no entries (the vertical ScrollBar will be visible in that case- may matter if you are doing something on CellBackgroundPaint)

So the code should read: (me refers to the listbox)

Dim VScrollBarVisble as Boolean

If me.ScrollBarVertical Then
   If NOT me.AutohideScrollBars Then
      VScrollBarVisble = True
   ElseIf me.HasHeading Then
      VScrollBarVisble = me.ListCount > 0 AND me.ColumnFromXY(me.Width-4, me.HeadingHeight + 3 ) = -1
   Else
      VScrollBarVisble = me.ListCount > 0 AND me.ColumnFromXY(me.Width-4, 3 ) = -1
  End if
Else
    VScrollBarVisble  = False
End if

Or as a computed property on a listbox subclass:

[code]Public Property VerticalScrollBarVisibleNow as Boolean
Get
If ScrollBarVertical Then

     If NOT AutohideScrollBars Then  Return True

     If HasHeading Then
        Return  ListCount > 0 AND ColumnFromXY(Width-4, HeadingHeight + 3 ) = -1
     Else
        Return  ListCount > 0 AND ColumnFromXY(Width-4, 3 ) = -1
     End if

  End if

End Get
End Property[/code]

I accelerated in this way (I went from 10 seconds to 0,26 seconds)
“disable scrolbar”

Listbox1.HasVerticalScrollbar = false

dim startTime as Double = Microseconds
dim i as integer

For i = 1 To 30000
ListBox1.AddRow(Str(i)+“b”,Str(i)+“b”,Str(i)+“b”,Str(i)+“b”)
next

dim endTime as Double = (Microseconds-startTime)/1000000

MsgBox(“Loading " + str(i) + " rows takes " + endTime.ToText + " seconds” )

Listbox1.HasVerticalScrollbar = true