65.9K
CodeProject is changing. Read more.
Home

Indeterminate or Bouncing Progress Bar

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (5 votes)

Dec 4, 2004

2 min read

viewsIcon

67245

downloadIcon

691

This is a beginning of XP's bouncing progress bar in C#.

Sample Image - screenshot.jpg

Introduction

After searching for many hours for the bouncing control bar you see in XP, I found this seemingly necessary control missing. So never having created a custom control before, I set out to do just that. My experience in .NET is limited, my time developing the control was limited, but my enthusiasm was not. Here is the result.

Special thanks to Microsoft for their introduction in the article HOW TO: Create a Smooth Progress Bar in Visual C# .NET.

I will only go over the main points as I expect everyone is familiar with programming....

When we update the value (which is automatically done by a forms timer), we need to see if it has gone to the edge of our control. This is determined base on the style of progressbar you selected. When it has gone as far as it can go, it goes the other way.

if (increasing)
{
  val++;
}
else
{
  val--;
}
//int oldValue = val;

// Make sure that the value does not stray outside the valid range.

if (val < min)
{
  val = min;
  increasing = true;
}
else if ((style == LocTecBarStyle.SmoothBounce) && (val > (max - blockWidth)))
{
  val = (max - blockWidth);
  increasing = false;
}
else if ((style == LocTecBarStyle.BlockBounce) && 
      (val > (max - (blockWidth * 2) - blockSpace)))
{
  val = (max - (blockWidth * 2) - blockSpace);
  increasing = false;
}

Next, we set up the area to invalidate. This is to minimize flickering. For a smooth progress bar we only invalidate a total width of two pixels. For the block style we invalidate a width of four pixels. This causes Windows to redraw those areas.

Rectangle invalidateLeftArea = this.ClientRectangle;
Rectangle invalidateRightArea = this.ClientRectangle;
Rectangle invalidateInnerLeftArea = this.ClientRectangle;
Rectangle invalidateInnerRightArea = this.ClientRectangle;
invalidateLeftArea.Width = 1;
invalidateRightArea.Width = 1;
invalidateInnerLeftArea.Width = 1;
invalidateInnerRightArea.Width = 1;
if (increasing)
{
  invalidateLeftArea.X = (val - 1);
  invalidateRightArea.X = (val + blockWidth - 1);
  if (style == LocTecBarStyle.BlockBounce)
  {
    invalidateRightArea.X = (val + (blockWidth * 2) + blockSpace - 1);
    invalidateInnerLeftArea.X = (val + blockWidth - 1);
    invalidateInnerRightArea.X = (val + blockWidth + blockSpace - 1);
  }
}
else
{
  invalidateRightArea.X = (val + blockWidth);
  invalidateLeftArea.X = (val);
  if (style == LocTecBarStyle.BlockBounce)
  {
    invalidateRightArea.X = (val + (blockWidth * 2) + blockSpace);
    invalidateInnerRightArea.X = (val + blockWidth + blockSpace);
    invalidateInnerLeftArea.X = (val + blockWidth);
  }
}
// Invalidate the intersection region only.

this.Invalidate(invalidateLeftArea);
this.Invalidate(invalidateRightArea);
if (style == LocTecBarStyle.BlockBounce)
{
  this.Invalidate(invalidateInnerLeftArea);
  this.Invalidate(invalidateInnerRightArea);
}

In the OnPaint event, we draw the rectangles. For the block style, we use the gradient for a true XP feel. There is a problem with it where (depending on the height of the control) it sometimes leaves a one pixel line between the gradients. If you know why, let me know. If I find out why, I will post the update.

Graphics g = e.Graphics;
SolidBrush brush = new SolidBrush(BarColor);
SolidBrush spaceBrush = new SolidBrush(this.BackColor);
float percent = (float)(val - min) / (float)(max - min);
switch (style)
{
  case (LocTecBarStyle.SmoothBounce):
  {
    Rectangle rect = this.ClientRectangle;
    // Set the area for drawing the progress.

    rect.Width = blockWidth;
    // Set the new location of the block

    rect.X = val;
    // Draw the progress meter.

    g.FillRectangle(brush, rect);
    break;
  }
  case (LocTecBarStyle.BlockBounce):
  {
    Rectangle gradientTop = this.ClientRectangle;
    Rectangle gradientBottom = this.ClientRectangle;
    Rectangle space = this.ClientRectangle;
    // Set the Width for drawing the progress.

    gradientTop.Width = (blockWidth * 2) + blockSpace;
    gradientBottom.Width = (blockWidth * 2) + blockSpace;
    space.Width = blockSpace;
    // Height

    gradientTop.Height = (int)(ClientRectangle.Height * .5) - 1;
    //(int)(ClientRectangle.Height * .5);

    gradientBottom.Height = ClientRectangle.Height - gradientTop.Height; 
    // Top

    gradientTop.Y = ClientRectangle.Y;
    gradientBottom.Y = gradientTop.Bottom;
    // Left

    gradientTop.X = val;
    gradientBottom.X = val;

    space.X = val + blockWidth;
    // Gradient

    LinearGradientMode lgmTop = LinearGradientMode.Vertical;
    using (LinearGradientBrush lgbTop = 
       new LinearGradientBrush(gradientTop, this.BackColor, BarColor, lgmTop))
    g.FillRectangle(lgbTop, gradientTop);
    LinearGradientMode lgmBottom = LinearGradientMode.Vertical;
    using (LinearGradientBrush lgbBottom = 
       new LinearGradientBrush(gradientBottom, BarColor, this.BackColor, lgmBottom))
    g.FillRectangle(lgbBottom, gradientBottom);
    // Draw the progress meter.

    g.FillRectangle(spaceBrush, space);
    break;
  }
}
// Draw a three-dimensional border around the control.

Draw3DBorder(g);
// Clean up.

brush.Dispose();
g.Dispose();

I have not optimized, perfected nor managed properly this code. I am open to criticisms, opinions, ideas, suggestions, etc. One last mention. If you are performing an intense task you will want to put that task in a separate thread so that this control can update its interface.