Thursday, December 11, 2008

How to set the size and position of a scroll bar thumb

The WPF scrollbars seem to be unduly difficult to use. This post describes why and defines some extension methods to make life a little easier.

The ScrollBar class has these properties to specify the thumb location and the thumb size:
  • Value - represents the thumb position
  • ViewportSize - represents the thumb location
Value varies between Maximum and Minimum. So if Maximum=100, Minimum=0 and Value=50. Then the thumb is in the middle, this is definitely the more sensible of the two properties. However, it's not great: it doesn't directly represent the absolute position of the thumb on the track.

ViewportSize varies between 0 and double.MaxValue. What!? What use is this to anyone?

What you nearly always have when you are implementing your own scrolling is the length of the thumb and the absolute position of the thumb. Here are some extension methods that convert these to and from the WPF properties.
public static class ScrollBarExtensions
{
    public static double GetThumbCenter(this ScrollBar s)
    {
        double thumbLength = GetThumbLength(s);
        double trackLength = s.Maximum - s.Minimum;        

        return thumbLength / 2 + s.Minimum + (s.Value - s.Minimum) * 
            (trackLength - thumbLength) / trackLength;
    }

    public static void SetThumbCenter(this ScrollBar s, double thumbCenter)
    {
        double thumbLength = GetThumbLength(s);
        double trackLength = s.Maximum - s.Minimum;

        if (thumbCenter >= s.Maximum - thumbLength / 2)
        {
            s.Value = s.Maximum;
        }
        else if (thumbCenter <= s.Minimum + thumbLength / 2)
        {
            s.Value = s.Minimum;
        }
        else if (thumbLength >= trackLength )
        {
            s.Value = s.Minimum;
        }
        else
        {
            s.Value = s.Minimum + trackLength  * 
                ((thumbCenter - s.Minimum - thumbLength / 2) 
                / (trackLength - thumbLength));
        }
    }

    public static double GetThumbLength(this ScrollBar s)
    {
        double trackLength = s.Maximum - s.Minimum;
        return trackLength * s.ViewportSize / 
            (trackLength + s.ViewportSize);
    }

    public static void SetThumbLength(this ScrollBar s, double thumbLength)
    {
        double trackLength = s.Maximum - s.Minimum;

        if (thumbLength < 0)
        {
            s.ViewportSize = 0;
        }
        else if (thumbLength < trackLength )
        {
            s.ViewportSize = trackLength * thumbLength / (trackLength - thumbLength);
        }
        else
        {
            s.ViewportSize = double.MaxValue;
        }
    }
}


ScrollBarExtensions.cs



2 comments:

Mike Pelton said...

Much appreciated - the guys that wrote WPF are pretty smart so am sure there's good reason for the scrollbar/thumb relationship being so arcane (or maybe it was just a Friday :-)) but your code's saved me walking the exact same path!!

Dan Lamping said...

I agree - they've certainly gone with this implementation for some good reason. It would just be nice to know what that reason is.