Proportional Scrollbar Handles Demo

I admit that I’ve struggled with scrollbars since the Crossbasic days: Specifically, how to get the scrollbar handle to be proportional to the content height. Facing the problem again, I didn’t find the ideal solution in the forums, so I put together a simple demo project:

The code:

Var contentHeight As Double = Canvas1.Height
Var visibleFractionOfContentHeight As Double = contentHeight / Me.Height

// Make the scrollbar handle proportional
Scrollbar1.MaximumValue = Ceil((visibleFractionOfContentHeight - 1) * 20) // 20 is magic number that will put the scrollbar at 50%

// Scroll the content
Canvas1.Top = (Me.Height - contentHeight) * Scrollbar1.Value / Scrollbar1.MaximumValue

“20” is a magic number. Would be smoother if .MaximumValue were floating point rather than an Integer, but it works. And I’m open to better solutions.

More output:

5 Likes

Here’s a project that has a DesktopScrollbar subclass called ProportionalDesktopScrollbar.

It has one new function, GetContentOffset, that takes the content size and visible size, computes the proportional handle size, and returns the content offset:

Public Function GetContentOffset(contentSize as Double, visibleSize as Double) As Double
  Var visibleFractionOfContentHeight As Double = contentSize / visibleSize
  
  // Make the handle proportional
  Var maxValue As Double = Ceil((visibleFractionOfContentHeight - 1) * 20) // 20 is magic number that will put a scrollbar at 50%
  Me.MaximumValue = maxValue
  
  If maxValue = 0 Then Return 0
  
  // Return the scroll offset
  Return (visibleSize - contentSize) * (Me.Value / Me.MaximumValue)
  
End Function

Call this when resizing the window or when the scrollbar’s value is changed. In the demo project:

Canvas1.Top = ScrollBar1.GetContentOffset(Canvas1.Height, Me.Height)

The class will work with horizontal or vertical scrollbars.

4 Likes

Bug fix: 20 is not a magic number—it’s the PageStep value. Using a high PageStep value will increase the granularity of the scrollbar.

Public Function GetContentOffset(contentSize as Double, visibleSize as Double) As Double
  Var visibleFractionOfContentHeight As Double = contentSize / visibleSize
  
  // Make the handle proportional
  Var maxValue As Double = Ceil((visibleFractionOfContentHeight - 1) * Me.PageStep) // Higher PageStep values offer more granularity in the scrollbar's position
  Me.MaximumValue = maxValue
  
  If maxValue = 0 Then Return 0
  
  // Return the scroll offset
  Return (visibleSize - contentSize) * (Me.Value / Me.MaximumValue)
  
End Function

I’ve updated both of the linked projects, above.

4 Likes

Is there any way to change the width of a scroolbar on a webview?

Sorry; I haven’t done any work with XOJO web apps yet.

Thanks a lot, this was really helpful when I was struggling with getting proportional scrollbars to work. In this area the documentation leaves much to be desired. Eventually I’ve made a few changes to your code, like splitting up the method in two.

First I have declared a private property ScrollRange of ProportionalScrollbar:

Private Property ScrollRange As Integer

The first method AdjustMaximum only needs to be called whenever the size of either the content or the display area changes:

Public Sub AdjustMaximum(ContentSize as Integer, VisibleSize as Integer)
  ScrollRange = ContentSize - VisibleSize
  MaximumValue = if(ContentSize < VisibleSize, 0, (ScrollRange * PageStep + VisibleSize) \ VisibleSize)
End Sub

GetContentOffset (to be called within a ValueChanged event handler) takes no parameters anymore:

Public Function GetContentOffset() As Integer
  return if(MaximumValue = 0, 0, -ScrollRange * Value \ MaximumValue)
End Function

I have removed all the Double variables – the properties of DesktopScrollbar take Integer values anyway and there is no need for intermediate Double values either. As a matter of principle I think that whenever something can be achieved with Integer arithmetic it should be.

1 Like

One could also add an event handler to ProportionalScrollbar so one can scroll using the mouse wheel:

Function MouseWheel(x As Integer, y As Integer, deltaX As Integer, deltaY As Integer) Handles MouseWheel as Boolean
  if me.Width > me.Height then
    me.Value = me.Value + deltaX
  else
    me.Value = me.Value + deltaY
  end if
  
  return true
End Function
1 Like