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

A variation on the default TrackBar

0.00/5 (No votes)
17 Aug 2004 1  
Multi-Color and multi-directional TrackBar.

Sample Image - ColorTrackBar.jpg

Introduction

I was looking for a better looking TrackBar control, when I stumbled upon Alan Zhao's ColorProgressBar code and saw how simple it would be to create my own TrackBar control. So, I came up with the ColorTrackBar. First, let's get some basic syntax down for naming the parts or areas of this control.

  • Bar - This is the background area of the control.
  • Tracker - This is the user control portion of the ColorTrackBar which will move to set the value.

Control Properties

  • BarBorderColor - Sets/Gets the color to be used when drawing the bar's border.
  • BarColor - Sets/Gets the color of the control's Bar.
  • TrackerBorderColor - Sets/Gets the Tracker's border color.
  • TrackerColor - Sets/Gets the Tracker's color.
  • Minimum - Sets/Gets the lowest possible number that the ColorTrackBar control can return.
  • Maximum - Sets/Gets the highest value the control can return.
  • Value - Sets/Gets the current position of the Tracker relative to the maximum and minimum values.
  • MaximumValueSide - This property allows the user to decide which direction on the control will increase the Value property, the opposite will obviously decrease Value. Possible values are Left, Right, Top, and Bottom.
  • BarOrientation - Select either Horizontal or Vertical.
  • ControlCornerStyle - Select either Square or Rounded corners.
  • TrackerSize - Specify the width or height of the tracker depending on the BarOrientation selection. Note: if you have selected Rounded corners, you cannot set the Tracker size.

Control Events

  • Scroll - This event is fired when the position of the Tracker is changed.
  • ValueChanged - This event fires when the numeric value of the Value property is changed.

Using The Control

  1. Download source code and unzip it.
  2. Open VS.NET's Tool menu and select "Add/Remove ToolBox Items".
  3. Click "Browse", and navigate to the ColorTrackBar.dll in the bin\Release directory of the source code.
  4. Click Open and then OK, the ColorTrackBar control will now be in your ToolBox, just drag the control onto your form.
  5. Set the control's properties, and wire-up an event handle, and you're ready to go.

Inside the Code

There are probably three areas of interest within the source code for this control, the first being drawing the rounded corners:

protected GraphicsPath DrawRoundedCorners(Rectangle Rect,
                Color BorderColor,Graphics g)
{
  GraphicsPath gPath = new GraphicsPath();
  try
  {
    Pen LinePen = new Pen(BorderColor,borderWidth+1);
    switch(barOrientation)
    {
      case Orientations.Horizontal:
        Rectangle LeftRect,RightRect;
        LeftRect=new Rectangle(Rect.X,Rect.Y+1,
                  Rect.Height-1,Rect.Height-2);
        RightRect = new Rectangle(Rect.X+
                    (Rect.Width-Rect.Height),Rect.Y+1,
                    Rect.Height-1,Rect.Height-2);
        //build shape

        gPath.AddArc(LeftRect,90,180);
        gPath.AddLine(LeftRect.X+LeftRect.Width/2+2,
              LeftRect.Top+1,RightRect.X+(RightRect.Width/2)-1,
              RightRect.Top+1);
        gPath.AddArc(RightRect,270,180);
        gPath.AddLine(RightRect.X+(RightRect.Width/2),
                RightRect.Bottom, LeftRect.X+(LeftRect.Width/2),
                LeftRect.Bottom);

        gPath.CloseFigure();
        g.DrawPath(LinePen,gPath);
        break;
      case Orientations.Vertical:
        Rectangle TopRect,BotRect;
        TopRect=new Rectangle(Rect.X+1,Rect.Y,
                    Rect.Width-2,Rect.Width-1);
        BotRect = new Rectangle(Rect.X+1,Rect.Y+
                    (Rect.Height-Rect.Width),
                    Rect.Width-2,Rect.Width-1);
        //build shape

        gPath.AddArc(TopRect,180,180);
        gPath.AddLine(TopRect.Right,
                      TopRect.Y+TopRect.Height/2,
                      BotRect.Right,
                      BotRect.Y+BotRect.Height/2+1);
        gPath.AddArc(BotRect,0,180);
        gPath.AddLine(BotRect.Left+1,
                      BotRect.Y+BotRect.Height/2-1,
                      TopRect.Left+1,TopRect.Y+TopRect.Height/2+2);
        gPath.CloseFigure();
        g.DrawPath(LinePen,gPath);
        break;
      default:
        break;
    }
  }
  catch(Exception Err)
  {
    throw new Exception("DrawRoundedCornersException: "
                                         +Err.Message);
  }
  return gPath;
}

The rounded corners are created by drawing two half-circles based on the control's height. First, I calculated two sub-rectangles from the ClientRectangle in which to draw the circles, then just connected the open ends of the half-circles for the rounded control.

The second area of interest would be the painting of the rounded control, this is where I followed Alan Zhao's lead, using the GradientBrush to give it a 3-D appearance.

protected void PaintPath(GraphicsPath PaintPath,
                 Color PathColor,Graphics g)
{
  Region FirstRegion,SecondRegion;
  FirstRegion = new Region(PaintPath);
  SecondRegion= new Region(PaintPath);
  //

  // Fill background

  //

  SolidBrush bgBrush = new SolidBrush(ControlPaint.Dark(PathColor));
  g.FillRegion(bgBrush, new Region(PaintPath));
  bgBrush.Dispose();
  //

  // The gradient brush

  //

  LinearGradientBrush brush;
  Rectangle FirstRect,SecondRect;
  Rectangle RegionRect = Rectangle.Truncate(PaintPath.GetBounds());
  switch(barOrientation)
  {
    case Orientations.Horizontal:
      FirstRect= new Rectangle(RegionRect.X,RegionRect.Y,
                 RegionRect.Width, RegionRect.Height / 2);
      SecondRect=new Rectangle(RegionRect.X,
                 RegionRect.Height / 2, RegionRect.Width,
                 RegionRect.Height / 2);
      //only get the bar region

      FirstRegion.Intersect(FirstRect);
      SecondRegion.Intersect(SecondRect);
      // Paint upper half

      brush = new LinearGradientBrush(
      new Point(FirstRect.Width/2,FirstRect.Top),
          new Point(FirstRect.Width/2,FirstRect.Bottom),
          ControlPaint.Dark(PathColor),
          PathColor);
      g.FillRegion(brush, FirstRegion);
      brush.Dispose();
      // Paint lower half

      // (SecondRect.Y - 1 because there would be

      // a dark line in the middle of the bar)

      brush = new LinearGradientBrush(
                  new Point(SecondRect.Width/2,
                  SecondRect.Top-1),
                  new Point(SecondRect.Width/2,
                  SecondRect.Bottom),
                  PathColor,
                  ControlPaint.Dark(PathColor));
      g.FillRegion(brush, SecondRegion);
      brush.Dispose();
      break;
    case Orientations.Vertical:
      FirstRect= new Rectangle(RegionRect.X,RegionRect.Y,
                     RegionRect.Width/2, RegionRect.Height);
      SecondRect=new Rectangle(RegionRect.Width / 2,
                     RegionRect.Y, RegionRect.Width/2,
                     RegionRect.Height);
      //only get the bar region

      FirstRegion.Intersect(FirstRect);
      SecondRegion.Intersect(SecondRect);
      // Paint left half

      brush = new LinearGradientBrush(
              new Point(FirstRect.Left, FirstRect.Height/2),
              new Point(FirstRect.Right,FirstRect.Height/2),
              ControlPaint.Dark(PathColor),
              PathColor);
      g.FillRegion(brush, FirstRegion);
      brush.Dispose();
      // Paint right half

      // (SecondRect.X - 1 because there would be a dark line

      // in the middle of the bar)

      brush = new LinearGradientBrush(
              new Point(SecondRect.Left - 1,SecondRect.Height/2),
              new Point(SecondRect.Right,SecondRect.Height/2),
              PathColor,
              ControlPaint.Dark(PathColor));
      g.FillRegion(brush, SecondRegion);
      brush.Dispose();
      break;
    default:
      break;
  }
}

First, I used GraphicsPath.GetBounds to get a RectangleF object which I then truncate into a regular Rectangle object. From there, I calculate the upper/lower or left/right halves of the control. In order to "filter" out the corners of the rectangle, I then get the Region that intersects both the GraphicPath's rectangle and the GraphicsPath shape (the rounded corners). I then just fill the two halves with background color.

Finally, I think the movement of the Tracker needs some explaining. I have never created any control that moves like this one, so forgive my kludgey solution. All the important code for the movement is in the WndProc() method.

I handle three messages:

  1. WM_LBUTTONDOWN (0x0201)
  2. WM_LBUTTONUP (0x0202)
  3. WM_MOUSEMOVE (0x0200)
if(m.Msg==0x0201)
{
  Point CurPoint=new Point(LowWord((uint)m.LParam),
                      HighWord((uint)m.LParam));
  if(trackRect.Contains(CurPoint))
  {
    if(!leftbuttonDown)
    {
      leftbuttonDown=true;
      switch(this.barOrientation)
      {
        case Orientations.Horizontal:
          mousestartPos= CurPoint.X-trackRect.X;
          break;
        case Orientations.Vertical:
          mousestartPos= CurPoint.Y-trackRect.Y;
          break;
      }
    }
  }
  else
  {
    int OffSet=0;
    switch(this.barOrientation)
    {
      case Orientations.Horizontal:
        if(trackRect.Right+(CurPoint.X-trackRect.X
                 -(trackRect.Width/2))>=this.Width)
          OffSet=this.Width-trackRect.Right-1;
        else if(trackRect.Left+(CurPoint.X-
               trackRect.X-(trackRect.Width/2))<=0)
          OffSet=(trackRect.Left-1)*-1;
        else
          OffSet=CurPoint.X-trackRect.X-(trackRect.Width/2);
        trackRect.Offset(OffSet,0);
        trackerValue=(int)( ((trackRect.X-1) *
           (barMaximum-barMinimum))/(this.Width-trackSize-2));
    if(maxSide==Poles.Left)
      trackerValue=(trackerValue-(barMaximum-barMinimum))*-1;
        break;
      case Orientations.Vertical:
        if(trackRect.Bottom+(CurPoint.Y-trackRect.Y-
                  (trackRect.Height/2))>=this.Height)
          OffSet=this.Height-trackRect.Bottom-1;
        else if(trackRect.Top+(CurPoint.Y-
                trackRect.Y-(trackRect.Height/2))<=0)
          OffSet=(trackRect.Top-1)*-1;
        else
          OffSet=CurPoint.Y-trackRect.Y-(trackRect.Height/2);
        trackRect.Offset(0,OffSet);
        trackerValue=(int)( ((trackRect.Y-1) *
           (barMaximum-barMinimum))/(this.Height-trackSize-2));
    if(maxSide==Poles.Top)
      trackerValue=(trackerValue-(barMaximum-barMinimum))*-1;
        break;
      default:
        break;
    }
    trackerValue+=barMinimum;
    this.Invalidate();
    if(OffSet!=0)
    {
      OnScroll();
      OnValueChanged();
    }
  }
  this.Focus();
}
//WM_MOUSEMOVE

if(m.Msg==0x0200)
{
  int OldValue=trackerValue;
  Point CurPoint=new Point(LowWord((uint)m.LParam),
                     HighWord((uint)m.LParam));
  if(leftbuttonDown && ClientRectangle.Contains(CurPoint))
  {
    int OffSet=0;
    try
    {
      switch(this.barOrientation)
      {
        case Orientations.Horizontal:
          if(trackRect.Right+(CurPoint.X-trackRect.X
                         -mousestartPos)>=this.Width)
            OffSet=this.Width-trackRect.Right-1;
          else if(trackRect.Left+(CurPoint.X-
                       trackRect.X-mousestartPos)<=0)
            OffSet=(trackRect.Left-1)*-1;
          else
            OffSet=CurPoint.X-trackRect.X-mousestartPos;
          trackRect.Offset(OffSet,0);
          trackerValue=(int)( ((trackRect.X-1) *
             (barMaximum-barMinimum))/(this.Width-trackSize-2));
      if(maxSide==Poles.Left)
        trackerValue=(trackerValue-(barMaximum-barMinimum))*-1;
          break;
        case Orientations.Vertical:
          if(trackRect.Bottom+(CurPoint.Y-
              trackRect.Y-mousestartPos)>=this.Height)
            OffSet=this.Height-trackRect.Bottom-1;
          else if(trackRect.Top+(CurPoint.Y-
              trackRect.Y-mousestartPos)<=0)
            OffSet=(trackRect.Top-1)*-1;
          else
            OffSet=CurPoint.Y-trackRect.Y-mousestartPos;
            trackRect.Offset(0,OffSet);
            trackerValue=(int)( ((trackRect.Y-1) *
               (barMaximum-barMinimum))/(this.Height-trackSize-2));
            if(maxSide==Poles.Top)
          trackerValue=(trackerValue-(barMaximum-barMinimum))*-1;
            break;
      }
    }
    catch(Exception){}
    finally
    {
      //force redraw

      trackerValue+=barMinimum;
      this.Invalidate();
      if(OffSet!=0)
      {
        OnScroll();
        OnValueChanged();
      }
    }
  }
}
WM_LEFTBUTTONDOWN

Basically, if the user clicks inside the Tracker's region, I save that initial point in mousestartPos, which I then use to calculate the offset of the Tracker's rectangle or region when the user drags the Tracker. If the initial point is not in the Tracker's region, but still within the Bar's region, I jump the Tracker's center to the left click position.

WM_MOUSEMOVE

When I receive the MOVE message, I check to see that the left button is down with my leftbuttonDown state variable, and offset the Tracker rectangle based on the initial start position, the current mouse position, and the current Tracker rectangle. Then, I calculate the Value (trackerValue) based on the new Tracker rectangle.

WM_LBUTTONUP
if(m.Msg==0x0202)
{
  leftbuttonDown=false;
}

When the left button up message is received, I simply set my state variable back to false to stop any movement, and reset allows the mousestartPos to be reset next time the user clicks on the control.

Conclusion

I removed many of the standard Control events from the designer using the ControlDesigner class. These can be restored very easily by editing the ColorTrackBar source code.

I hope others find this control useful, please send me any suggestions or comments.

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