Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Cool Scrollbar - Scrollbar like Windows Media Player's

0.00/5 (No votes)
3 May 2005 1  
A cool scrollbar control.

Introduction

Hans Dietrich did a good job in creating a Windows Media Player like XScrollbar; however, for most Visual Basic programmers, it's a little bit inconvenient to use that expressly in Visual Basic, so I recently wrote a VB edition of Dietrich's XScrollbar – Cool Scrollbar, using GDI+ to draw everything. It currently has:

  • Optional color displayed on thumb, in channel, and on control border.
  • Hand cursor is displayed when mouse hovers over thumb.
  • Thumb color is changed to "hover color" when mouse hovers over thumb.
  • Clicking in channel moves thumb to that spot.
  • Left and right arrow buttons move thumb by one unit, and if kept pushed, the thumb would keep moving.
  • ValueChanged event is raised when value changes.
  • Both horizontal and vertical versions of the scrollbar.

This control is still in its infantile period and many things could be added to it. I would appreciate any advice to enhance its performance.

First things first

Some introduction to GDI+

GDI+ differs from GDI in two aspects:

  1. GDI+ expands the functions of GDI by means of providing new functions (i.e., gradient brush and alpha blending).
  2. GDI+ redefines programming model to make it easier and flexible for graphics programming.

GDI+ can be used to show images, draw custom shapes and lines, draw strings, and even image transformation. In our project, we use it to draw and fill shapes, and show images.

In order to use GDI+, a Graphics object must be created and instantiated. For instance:

Dim g as Graphics = Button1.CreateGraphics

If you want to draw lines or shapes, a Pen class is needed:

Dim myPen as new Pen(Color.Red)

Then you can draw lines and shapes:

g.DrawLine(myPen, 1, 1, 45, 65) 'draw line
g.DrawBezier(myPen, 15, 15, 30, 30, 45, 30, 87, 20)  'draw Bezier curve
g.DrawEllipse(myPen, New Rectangle(33, 45, 40, 50))  'draw ellipse
g.DrawPolygon(myPen, New PointF() {New PointF(1, 1), _
   New PointF (20, 10), New PointF(5, 4), New PointF(100, 2), _
   New PointF(200, 50), New PointF(39, 45)})  'draw polygon

To fill a specific area, a Brush object is needed:

Dim myBrush as New SolidBrush(Color.Red)

In fact, several different brushes exist in GDI+, as listed below:

Brush class

Description

SolidBrush

Pure color

HatchBrush

Similar to SolidBrush but you could select the color you want from preset colors

TextureBrush

Use texture (i.e. picture)

LinearGradientBrush

Gradient

PathGradientBrush

Complex gradient

The ones we uses in our project are SolidBrush, LinearGradientBrush, and PathGradientBrush.

If you want to show an image, an Image object must be created and instantiated. For example:

Dim myBitmap as New Bitmap(System.Environment.GetFolderPath _
      (System.Environment.SpecialFolder.MyPictures))

Then you can load this image to the Graphics object in a specific location:

g.DrawImage(myBitmap, 1, 1)

Don't forget to free memory after using the Graphics and Image objects:

g.Dispose()
myBitmap.Dispose()

Please refer to GDI+ references in MSDN for details.

Nomenclature

The following image shows the components and nomenclature of a Cool Scrollbar:

Drawing Cool Scrollbar

Before I started, a list of the member variables and their meanings should be made clear. In the class CBar, I used the following member variables:

    'height of the arrow bitmap
    Private Const BitmapHeight As Byte = 12
    'width of the arrow bitmap
    Private Const BitmapWidth As Byte = 25

    'max possible value of the scrollbar
    Private m_nMaxValue As Long = 100
    'min possible value of the scrollbar
    Private m_nMinValue As Long = 0
    'absolute value of the scrollbar
    Private m_nValue As Long = 0
    'value of the scrollbar
    Private m_nRealValue As Long = 0

    'left arrow image
    Private m_imgLeftImage As Bitmap
    'right arrow image
    Private m_imgRightImage As Bitmap

    'left value of Thumbleft
    Private m_fThumbLeft As Single = 26.0F
    'flag to indicate if mouse is hover the thumb
    Private m_bMouseOnThumb As Boolean = False  
    ' flag to indicate whether left mouse button pushed
    Private m_bMouseDown As Boolean = False
    
    'which button is clicked: Left-True; Right-False
    Private m_bArrowClicked As Boolean      
       
    '''color properties
    'Border color
    Private m_cBorderColor As Color = Color.White
    'right track begin color
    Private m_cRightChannelBeginColor As Color = Color.Honeydew
    'right track end color
    Private m_cRightChannelEndColor As Color = Color.Gray
    'left track begin color
    Private m_cLeftChannelBeginColor As Color = Color.Green
    'left track end color
    Private m_cLeftChannelEndColor As Color = Color.White  
    'thumb's fill color(the left and right Half eclipse's color)
    Private m_cThumbFillColor As Color = Color.Blue
    'center color of the thumb
    Private m_cThumbRectColor As Color = Color.LightYellow  
    '''end color properties

    'layout, horizontal or vertical
    Private m_BarLayout As BarLayout = BarLayout.Horizontal

Step 1: draw arrows and track border

As it might be a little complicated to draw arrow buttons and the user might want to customize the arrow picture, I use bitmaps as arrow picture and directly draw them onto each side of the control. Initially, I restricted the control's height to BitmapHeight+2 pixels, the arrow picture size is BitmapWidth * BitmapHeight (pixels). You can modify these values later. If the user doesn't specify a different picture, it will use the default arrows, the same as those in Dietrich's. For the track border, I draw a rectangle along the entire side of the control.

Dim gTrack As Graphics = Me.CreateGraphics
'draw track border
gTrack.DrawRectangle(New Pen(m_cBorderColor), _
  0, 0, Me.Width - 1, Me.Height - 1)

'draw arrow to each side of the track
gTrack.DrawImage(m_imgLeftImage, 1, 1)
gTrack.DrawImage(m_imgRightImage, Me.Width - BitmapWidth - 1, 1)

Step 2: draw channels

For both the channels, I use the LinearGradientBrush to fill in the area, which gives the entire control a 3D look. The user can customize the color of each channel through corresponding properties, which I will describe later. The left channel and right channel brush are defined like this:

Dim rightBrush As New Drawing2D.LinearGradientBrush(ClientRectangle,_ 
        m_cRightChannelBeginColor, m_cRightChannelEndColor, _
        Drawing2D.LinearGradientMode.Vertical)
Dim leftBrush As New Drawing2D.LinearGradientBrush(ClientRectangle, _
        m_cLeftChannelBeginColor,_ m_cLeftChannelEndColor, _
        Drawing2D.LinearGradientMode.BackwardDiagonal)

There are two points that must be kept in mind when drawing the channels:

  1. you shouldn't fill the track border;
  2. both channels exclude the arrows, so the arrow area should not be overlaid.

Consequently, the rectangle area of each channel is defined as below:

Dim fTmpRightChannelWidth As Single
Dim LeftChannel As Rectangle = _
   New Rectangle(BitmapWidth + 1, 2, 0, BitmapHeight - 2), _
   RightChannel As RectangleF

LeftChannel.Width = CalValue() - BitmapWidth + BitmapHeight / 2
If LeftChannel.Width <= 0 Then
  LeftChannel.Width = BitmapHeight / 2
  fTmpRightChannelWidth = Me.Width - BitmapWidth * 2 - 2
  RightChannel = New RectangleF(BitmapWidth + 1, 1, _
    fTmpRightChannelWidth, BitmapHeight)
'The function CalValue() returns the current
' thumb's left edge's x-coordinate.
Private Function CalValue() As Single
    Return BitmapWidth + 1 + (Me.Width - BitmapWidth * 3 - 2) _
       * m_nValue / (m_nMaxValue - m_nMinValue)
End Function

Next, fill in the corresponding area using the brush defined above:

gTrack.FillRectangle(rightBrush, RightChannel)
gTrack.FillRectangle(leftBrush, LeftChannel)
gTrack.DrawRectangle(New Pen(Color.Gray), LeftChannel.X, LeftChannel.Y, _
  LeftChannel.Width, LeftChannel.Height - 1)

Step 3: Draw thumb

Until very recently, I haven't figured out a good-looking style of the thumb, so the current result of the style of the thumb might look a bit ugly; however, the drawing process is almost the same.

The size of the thumb is the same as that of the arrow. Before drawing the thumb, you must first calculate the position of the thumb. This is done by the function CalValue(), as described earlier. The thumb area is composed of a pie area at each side and a rectangle area in the center. I use a GraphicsPath class to create such an area and use a PathGradientBrush to fill in the area.

        Dim gThumb As Graphics = Me.CreateGraphics
        'pen to draw the edge of the area
        Dim linePen As New Pen(Color.Gray, 1)
        Dim fX(3) As Single

        'initialize edge values--------------------------------------
        fX(0) = CalValue()
        If (m_BarLayout = BarLayout.Horizontal) Then
            fX(1) = fX(0) + BitmapHeight / 2
            fX(2) = fX(1) + (BitmapWidth - BitmapHeight) / 2 '7
        Else
            fX(1) = fX(0) + BitmapWidth / 2
            fX(2) = fX(1) + (BitmapHeight - BitmapWidth) / 2 '7
        End If
        '''----------------------------------------------------------

        'define path and brushes
        Dim rectPath As New Drawing2D.GraphicsPath
        'path that will constitute the thumb area
        If (m_BarLayout = BarLayout.Horizontal) Then
            Dim rect2Fill As New RectangleF(fX(1), 1.0F, _
                BitmapWidth - BitmapHeight, BitmapHeight)
                'center rectangle area of the thumb
            Dim rect2Fill As New RectangleF(fX(1), 1.0F, fX(2)_
               - fX(0), BitmapHeight)
               'center rectangle area of the thumb
            rectPath.AddArc(fX(0), 0.3F, BitmapHeight, _
                        BitmapHeight, 90, 180) 'left pie
            rectPath.AddRectangle(rect2Fill) 'center
            rectPath.AddArc(fX(2), 0.3F, BitmapHeight, _
                        BitmapHeight, 90, -180) 'right pie
        Else
            Dim rect2Fill As New RectangleF(1.0F, fX(2), _
                    BitmapWidth, 2 * fX(1) - fX(0) - fX(2))
            rectPath.AddArc(0.3F, 2 * fX(1) - fX(0), _
                    BitmapWidth, BitmapWidth, 0, 180) 'upper pie
            rectPath.AddRectangle(rect2Fill) 'center
            rectPath.AddArc(0.3F, fX(2) - fX(1) + fX(0), _
                    BitmapWidth, BitmapWidth, -180, 180) 'down pie
        End If
        Dim rectBrush As New Drawing2D.PathGradientBrush(rectPath)
        rectBrush.CenterColor = m_cThumbRectColor
        Dim rectColors As Color() = {m_cThumbFillColor, _
            m_cThumbFillColor, m_cThumbFillColor, m_cThumbFillColor}
        rectBrush.SurroundColors = rectColors

        'draw the thumb
        If (m_BarLayout = BarLayout.Horizontal) Then
            gThumb.DrawArc(linePen, fX(0), 1.0F, _
                   BitmapHeight, BitmapHeight - 1, 90, 180)
            gThumb.DrawArc(linePen, fX(2), 1.0F, _
                   BitmapHeight, BitmapHeight - 1, -90, 180)
        Else
            gThumb.DrawArc(linePen, 1.0F, 2 * fX(1) - fX(0), _
                   BitmapWidth, BitmapWidth - 1, 0, 180)
            gThumb.DrawArc(linePen, 1.0F, fX(2) - fX(1) + fX(0), _
                   BitmapWidth, BitmapWidth - 1, 0, -180)
        End If
        gThumb.FillPath(rectBrush, rectPath)

Step 4: Add them together

Just do the jobs described in the first three steps every time the value of the scrollbar changes and the bar will appear like the one described at the beginning of the article.

Turn our scrollbar into a real Cool Scrollbar

Add overrides

Up to now, our scrollbar has its “face”, but is nothing more than a picture. In order to be a real scrollbar, first it should be able to be scrolled. With the help of bunches of override subs and functions inherited from the System.Windows.Forms.UserControl class, it is easy to realize this.

As described in the Nomenclature section, the scrollbar has three areas: the channels, the thumb and the arrow buttons. Thus when the mouse is clicked or moved in these areas, there should be different events. For more details, please refer to my code, this section is written as clear as possible so that you can understand it well.

Add properties

As for the properties, I currently enable the following:

Behavior

  • Value: the current value of the scrollbar (Long)
  • MinValue: minimum value of the scrollbar (Long)
  • MaxValue: maximum value of the scrollbar (Long)

Appearance

  • LeftArrow: left arrow image (Bitmap)
  • RightArrow: right arrow image (Bitmap)
  • LeftChannelBeginColor: begin color of the left channel brush (Color)
  • LeftChannelEndColor: end color of the left channel brush (Color)
  • RightChannelBeginColor: begin color of the right channel brush (Color)
  • RightChannelEndColor: end color of the right channel brush (Color)
  • ThumbFillColor: center color of the thumb area (Color)
  • ThumbRectColor: surrounding color of the thumb area (Color)
  • TrackBorderColor: color of the border of the track (Color)
  • ScrollbarLayout: the layout of the scrollbar, vertical or horizontal.

The way of enabling these properties is the same as the ordinary one, just remember to redraw the track every time the property value changes.

This is what they look like in a Windows application property window:

You can modify them as you like.

Add event

Currently the Cool Scrollbar only needs one event to function correctly: ValueChanged. Just raise this event each time the MouseUp event occurs.

Future enhancements

  • A more stylish thumb
  • Flexible size property
  • Optional tooltip text indicating the value of the scrollbar
  • More...

Acknowledgement

Thanks to Hans Dietrich for the idea of creating the XScrollBar, as well as the clear explanation of the drawing process.

History

Version 0.1 - October 12, 2004

  • Initial public release.

Version 0.2 - April 25, 2005

  • Add vertical version of the scrollbar.
  • Update the thumb drawing method.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here