Click here to Skip to main content
Licence CPOL
First Posted 7 Apr 2009
Views 40,359
Downloads 1,112
Bookmarked 43 times

gTrackBar - A Custom TrackBar UserControl (VB.NET)

By SSDiver2112 | 12 Jul 2010
TrackBar with custom coloring, value display, label, and increment buttons

1

2
1 vote, 6.3%
3
2 votes, 12.5%
4
13 votes, 81.3%
5
4.86/5 - 16 votes
1 removed
μ 4.79, σa 1.04 [?]

Introduction

I must have OCD (Obsessive Control Disorder). The standard TrackBar has annoyed me for a long time, but I just put up with it until now. I finally got tired of the boring appearance, having to code a TextBox or Label with it to display the value, and a Label for what it is. So, here is the TrackBar I needed, hope it helps you too. There are too many properties to just list them out like I normally would, so I decided to list the function of the property groups and a screenshot of the whole list.

  1. The Control group contains the control's Border, Value, and Orientation properties.
  2. FloatValue is the value that appears as the slider is moved with the mouse.
  3. Label is the text that appears above the TrackBar.
  4. Slider is the line, tick marks, and the slider button itself.
  5. UpDownButtons are the buttons at each end of the TrackBar to increment the value by one.
  6. ValueBox is the box displaying the value.

Properties

Building

The Building Region contains the routines to setup the layout, size, and positions for all parts of the control based on the properties set.

Painting

Override the Paint event to custom draw each piece of the control in the right place with the correct orientation.

    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)

        'Setup the Graphics
        Dim g As Graphics = e.Graphics
        g.SmoothingMode = SmoothingMode.AntiAlias
        g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias

        'Draw a Border around the control if requested
        If _BorderShow Then
            g.DrawRectangle(New Pen(_BorderColor), _
                0, 0, Me.Width - 1, Me.Height - 1)
        End If

        'Add the value increment buttons if requested
        If _UpDownShow Then DrawUpDnButtons(g)

        'Add the Line and Tick Marks
        DrawSliderLine(g)

        'Draw the Label Text if requested
        If _LabelShow Then
            DrawLabel(g)
            'g.DrawRectangle(Pens.Gray, rectLabel)
        End If

        'Add the Slider button
        DrawSlider(g)

        'Draw the Value above the Slider if requested
        If _FloatValue AndAlso IsOverSlider AndAlso _
          MouseState = eMouseState.Down Then
            DrawFloatValue(g)
        End If

        'Draw the Box displaying the value if requested
        If Not _ValueBox = eValueBox.None Then
            DrawValueBox(g)
        End If

        'Draw Focus Rectangle around control if requested 
        If _ShowFocus AndAlso Me.Focused Then
            ControlPaint.DrawFocusRectangle(g, New Rectangle( _
            2 + CInt(Not _BorderShow), 2 + CInt(Not _BorderShow), _
            Me.Width - ((2 + CInt(Not _BorderShow)) * 2), _
            Me.Height - ((2 + CInt(Not _BorderShow)) * 2)), _
            Color.Black, Me.BackColor)
        End If

    End Sub

I used basic GDI+ functions to draw each piece of the control. I will highlight some of the better parts.

In DrawUpDnButtons, I basically have a button that needs to be drawn one of four ways depending on the Orientation and which side of the control it is on. The button itself is just a rectangle with a gradient color fill. The trick was the arrow. I could have calculated each arrow position and points to create a separate GraphicsPath for each, but I used a Matrix instead to Rotate and/or Translate a simple GraphicsPath to the right position.

First set three points and add a line between them to create the triangular arrow ^ shape:

Dim gp As New GraphicsPath
Dim pts() As Point
Dim mx As New Matrix
pts = New Point() { _
        New Point(5, 0), _
        New Point(0, 5), _
        New Point(5, 10)}
gp.AddLines(pts)

For the left hand button, the GraphicsPath is oriented correctly so it just needs to be translated (moved) to the right position inside the button's rectangle.

With rectDownButton
    mx.Translate(5, CSng((rectDownButton.Y _
      + (rectDownButton.Height / 2)) - 6))
    gp.Transform(mx)
    g.DrawPath(pn, gp)
End With

For the right hand button, the GraphicsPath needs to be flipped, but there isn't a built-in function for flipping. Use this to flip the GraphicsPath horizontally instead: New Matrix(-1, 0, 0, 1, image width here, 0).

With rectUpButton
     mx = New Matrix(-1, 0, 0, 1, 5, 0)
     mx.Translate(.X + 9, 0, MatrixOrder.Append)
     gp.Transform(mx)
     g.DrawPath(pn, gp)
End With

For the upper button, the GraphicsPath needs to be rotated 90 degrees. Find the center point and RotateAt that point.

With rectDownButton
     mx.RotateAt(90, New PointF(gp.GetBounds.Width / 2, _
         gp.GetBounds.Height / 2))
     mx.Translate(CSng((rectDownButton.X + _
        (rectDownButton.Width / 2)) - 3), 4, MatrixOrder.Append)
     gp.Transform(mx)
     g.DrawPath(pn, gp)
End With

For the lower button, the GraphicsPath needs to be flipped again. Use this to flip the image vertically: New Matrix(1, 0, 0, -1, 0, image height here).

With rectUpButton
     mx = New Matrix(1, 0, 0, -1, 0, 10)
     mx.Translate(0, .Y + 6, MatrixOrder.Append)
     gp.Transform(mx)
     g.DrawPath(pn, gp)
End With

The CenterPoint and FocusScales used in the slider brushes are of type PointF. When you create a new property of type PointF, you will notice that it is grayed out in the PropertyGrid, but if a property is of type Point, it will edit correctly in the PropertyGrid. The problem is that there is no built-in TypeConverter for PointF. This is actually pretty easy to fix.

A PointF type property without a custom TypeConverter: it works in code, but you cannot edit it in the PropertyGrid.

With the PointFConverter, it performs just like the Point property in the PropertyGrid.

Create a property and add the TypeConverter attribute referencing the PointFConverter class we are about to add.

Private _SliderHighlightPt As PointF = New PointF(-5.0F, -2.5F)
<Category("Appearance Slider")> _
<Description("Point on the Slider for the Highlight Color")> _
<TypeConverter(GetType(PointFConverter))> _
Public Property SliderHighlightPt() As PointF
    Get
        Return _SliderHighlightPt
    End Get
    Set(ByVal value As PointF)
        _SliderHighlightPt = value
        Me.Invalidate()
    End Set
End Property

The PointFConverter class inherits ExpandableObjectConverter, and overrides the CanConvertFrom, ConvertFrom, and ConvertTo functions.

Friend Class PointFConverter : Inherits ExpandableObjectConverter

    Public Overloads Overrides Function CanConvertFrom( _
        ByVal context As System.ComponentModel.ITypeDescriptorContext, _
        ByVal sourceType As System.Type) As Boolean

        If (sourceType Is GetType(String)) Then
            Return True
        End If
        Return MyBase.CanConvertFrom(context, sourceType)

    End Function

    Public Overloads Overrides Function ConvertFrom( _
        ByVal context As System.ComponentModel.ITypeDescriptorContext, _
        ByVal culture As System.Globalization.CultureInfo, _
        ByVal value As Object) As Object

        If TypeOf value Is String Then
            Try
                Dim s As String = CType(value, String)
                Dim ConverterParts(2) As String
                ConverterParts = Split(s, ",")
                If Not IsNothing(ConverterParts) Then
                    If IsNothing(ConverterParts(0)) Then ConverterParts(0) = "-5"
                    If IsNothing(ConverterParts(1)) Then ConverterParts(1) = "-2.5"
                    Return New PointF(CSng(ConverterParts(0).Trim), _
                                      CSng(ConverterParts(1).Trim))
                End If
            Catch ex As Exception
                Throw New ArgumentException("Can not convert '" & _
                    CStr(value) & "' to type Corners")
            End Try
        Else
            Return New PointF(-5.0F, -2.5F)
        End If

        Return MyBase.ConvertFrom(context, culture, value)

    End Function

    Public Overloads Overrides Function ConvertTo( _
        ByVal context As System.ComponentModel.ITypeDescriptorContext, _
        ByVal culture As System.Globalization.CultureInfo, _
        ByVal value As Object, ByVal destinationType As System.Type) As Object

        If (destinationType Is GetType(System.String) _
            AndAlso TypeOf value Is PointF) Then

            Dim ConverterProperty As PointF = CType(value, PointF)
            ' build the string representation 
            Return String.Format("{0}, {1}", _
                    ConverterProperty.X, _
                    ConverterProperty.Y)
        End If
        Return MyBase.ConvertTo(context, culture, value, destinationType)

    End Function

End Class 'PointFConverter Class

Mouse Events

Here is where we check what part of the control the cursor is over and if the mouse button is pressed. Based on this information, the Value is adjusted accordingly.

Because the MouseDown, Click and so on are a one time deal, a Timer is needed see if the mouse is still down and if so change the value again. I didn't want it to run away as soon as it was clicked so there is a built in delay right after the mouse is clicked, then after the delay it will begin incrementing the value. The other issue is if the Min/Max span was big it would crawl super slow and small spans would zip too fast, so the Timer's interval is adjusted based on how big the span is.

Private Sub MouseTimer_Tick(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles MouseTimer.Tick
    'Check if mouse was just clicked
    If MouseHoldDownTicker < 5 Then
        MouseHoldDownTicker += 1
        'Interval was set to 100 on MouseDown
        'Tick off 5 times and then reset the Timer Interval
        '  based on the Min/Max span
        If MouseHoldDownTicker = 5 Then
            MouseTimer.Interval = CInt(Math.Max _
                (10, 100 - ((_MaxValue - _MinValue) / 10)))
        End If
    Else
        'Change the value until the mouse is released
        Me.Value += MouseHoldDownChange
    End If
End Sub

Key Events

I wanted to be able to adjust the Value by pressing the arrow keys. That sounded simple. I figured I would just check the e.KeyValue in the KeyUp event and adjust the Value accordingly. Well, not so simple. The problem is that the UserControl Inherits Button, which automatically handles the arrow keys differently. After the KeyUp event, the focus jumps to the next control in the Tab Order, even if you use e.Handled and e.SuppressKeyPress. I couldn't stop the focus change. Then, I thought I would use the KeyDown event, but guess what, the arrows are automatically ignored there. To fix this behavior, I override the IsInputKey function to allow the arrow keys. After this, the focus will not jump away anymore.

Protected Overrides Function IsInputKey( _
          ByVal keyData As System.Windows.Forms.Keys) As Boolean
    'Because a Usercontrol ignores the arrows in the KeyDown Event
    'and changes focus no matter what in the KeyUp Event
    'This is needed to fix the KeyDown problem
    Select Case keyData And Keys.KeyCode
        Case Keys.Up, Keys.Down, Keys.Right, Keys.Left
            Return True
        Case Else
            Return MyBase.IsInputKey(keyData)
    End Select
End Function

Private Sub gTrackBar_KeyUp(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp
    
    Dim adjust As Integer = _ChangeSmall
    If e.Shift Then
        adjust = _ChangeLarge
    End If

    Select Case e.KeyValue
        Case Keys.Up, Keys.Right
            Me.Value += adjust

        Case Keys.Down, Keys.Left
            Me.Value -= adjust
    End Select
End Sub

History

  • Version 1.0 - March 2009
  • Version 1.2 - April 2009
    • Added Focus Rectangle
    • Separated the Label into its own Rectangle area for better layout and sizing
    • General Layout Fixes
  • Version 1.3 - April 2009
    • Handles Negative ranges
  • Version 1.4 - July 2010
    • Added Image Slider
    • Added UpDownShow to Hide and turn off the Up/Down buttons
    • Added Timer for changing the value (after a short delay) until the mouse is released

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

SSDiver2112

Software Developer

United States United States

Member
I first got hooked on programing with the TI994A. After it finally lost all support I reluctantly moved to the Apple IIe. Thank You BeagleBros for getting me through. I wrote programs for my Scuba buisness during this time. Currently I am a Database manager and software developer. I started with VBA and VB6 and now having fun with VB.NET

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionIs it possible to change the position of the slider-button ? PinmemberFailed Noob3:42 14 Jan '12  
AnswerRe: Is it possible to change the position of the slider-button ? PinmemberSSDiver21127:54 14 Jan '12  
QuestionUse as scrollbar Pinmemberswehunter20007:22 7 Nov '11  
QuestionSlider image Pinmemberswehunter20006:19 21 Oct '11  
AnswerRe: Slider image PinmemberSSDiver21127:06 21 Oct '11  
Questionreset value to the middle PinmemberMember 70171546:58 2 Sep '11  
AnswerRe: reset value to the middle PinmemberSSDiver211211:20 2 Sep '11  
Questionreset value to the middle PinmemberMember 70171545:57 2 Sep '11  
QuestionRotating the slider PinmemberJohnnybj12:55 3 Jul '11  
AnswerRe: Rotating the slider PinmemberSSDiver211214:54 5 Jul '11  
GeneralRe: Rotating the slider PinmemberJohnnybj8:46 8 Jul '11  
GeneralRe: Rotating the slider PinmemberSSDiver211213:36 9 Jul '11  
QuestionFloatValue cutted out if longer then 4 characters [modified] Pinmembervideoed4:07 22 Jun '11  
AnswerRe: FloatValue cutted out if longer then 4 characters PinmemberSSDiver21124:49 24 Jun '11  
GeneralRe: FloatValue cutted out if longer then 4 characters Pinmembervideoed3:29 26 Jun '11  
Generalerrors if Orientation = Vertical Pinmemberwbp4711:01 11 Jan '11  
GeneralRe: errors if Orientation = Vertical PinmemberSSDiver211216:09 12 Jan '11  
Generalscroll event of gtrackbar PinmemberMember 35748258:55 11 Nov '10  
GeneralRe: scroll event of gtrackbar PinmemberSSDiver21122:18 12 Nov '10  
GeneralTick Pinmembert_nedelchev1:49 10 Nov '10  
GeneralRe: Tick PinmemberSSDiver21125:35 10 Nov '10  
GeneralRe: Tick Pinmembert_nedelchev22:41 10 Nov '10  
GeneralSet value on clicked point PinmemberSousuke_11:33 28 Jul '10  
GeneralRe: Set value on clicked point PinmemberSSDiver211212:07 28 Jul '10  
GeneralBorder when selected Pinmemberde_mts23:39 25 Mar '10  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120209.1 | Last Updated 12 Jul 2010
Article Copyright 2009 by SSDiver2112
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid