Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

A simple custom progressbar

4.84/5 (28 votes)
21 Dec 2009CPOL3 min read 75.2K   4.2K  
A simple custom progressbar.

Image 1

Introduction

In this article, I paint a simple customized progress bar with four sections and different colors depending on the status and % filled. The class is written to fit into my company's software, but it's not that hard to modify it for your own needs. A progress bar is a good way to show data of any type, since you can see the most "important" info from far away without reading the text. Adding different colors further enhances that.

I use the superb extensions that Arun Reginald posted a while ago: http://www.codeproject.com/KB/GDI-plus/ExtendedGraphicsII.aspx, to get the rounded corners.

Background

Back in VB 6 days, there were controls for everything. No matter what you wanted to do, there were always some controls that would do it. My company used a simple progress bar with some text on it to display production data from saw mills. In our new version of the software, we made a more good looking version of that form. We liked it until the day we had to remote control over 56K ;). The customers however did not. Too much non-important graphics they said. So we listened, and went back to the idea of progress bars. The one that comes with .NET is good in many ways, but sometimes, you just want more. My friend Google just didn't solve the problem this time, so I had to write my own.

Using the Code

Once imported and compiled, you can use this control like any other in Visual Studio, or you can add it like this:

C#
HarrProgressBar pgb = new HarrProgressBar();
pgb.Padding = new Padding(5);
pgb.LeftText = "1";
pgb.MainText = "47x100x5400 - 20/100";
pgb.FillDegree = 20;
pgb.RightText = "1";
pgb.StatusText = "Raw";
pgb.StatusBarColor = 0;
pgb.Size = s;
pgb.Anchor = AnchorStyles.Left | AnchorStyles.Right;
this.flowLayoutPanel1.Controls.Add(pgb);

This control is based on System.Windows.Forms.Panel, and thus supports all the normal behaviors like Anchor and Docking. I added an internal padding to create a distance between the actual panel area and the drawn area, to be able to switch the background color to get a "selected" look and feel.

The different color codes are hardcoded to keep it simple. To hide sections, just set the sectionsize to 0.

Points of Interest

I haven't really used GDI+ that much, so this was quite fun to do for a change. I soon realized that rounded rectangles are much more better looking. This wasn't that easy though, and I am really happy I found Arun's extensions, which made my life much easier. The tricky part was to calculate the areas to draw:

C#
private Rectangle GetLeftArea()
{
    return new Rectangle(
        Padding.Left,
        Padding.Top, 
        LeftBarSize,
        this.ClientRectangle.Height - Padding.Bottom - Padding.Top);
}

private Rectangle GetStatusArea()
{
    return new Rectangle(
        Padding.Left + LeftBarSize,
        Padding.Top,
        StatusBarSize,
        this.ClientRectangle.Height - Padding.Bottom - Padding.Top);
}

private Rectangle GetMainArea()
{
    return new Rectangle(
        Padding.Left + LeftBarSize + StatusBarSize,
        Padding.Top,
        Convert.ToInt32(((this.ClientRectangle.Width - 
          (Padding.Left + LeftBarSize + StatusBarSize + 
           RightBarSize + Padding.Right)) * FillDegree) / 100),
        this.ClientRectangle.Height - Padding.Bottom - Padding.Top);
}

private Rectangle GetMainAreaBackground()
{
    return new Rectangle(
           Padding.Left + LeftBarSize + StatusBarSize,
           Padding.Top,
           this.ClientRectangle.Width - (Padding.Left + 
             LeftBarSize + StatusBarSize + RightBarSize + Padding.Right),
           this.ClientRectangle.Height - Padding.Bottom - Padding.Top);
}

private Rectangle GetRightArea()
{
    return new Rectangle(
        this.ClientRectangle.Width - (RightBarSize + Padding.Right),
        Padding.Top, 
        RightBarSize,
        this.ClientRectangle.Height - Padding.Bottom - Padding.Top);
}

I had to have two different "Main" sections to be able to draw the text and the grey background. Once I have those rectangles, painting the sections is easy:

C#
public void paintThis(Graphics _graphics)
{
    // Textformat
    StringFormat f = new StringFormat();
    f.Alignment = StringAlignment.Center;
    f.LineAlignment = StringAlignment.Center;

    // Misc
    _graphics = this.CreateGraphics();
    System.Drawing.Drawing2D.LinearGradientBrush _LeftAndRightBrush = 
      new LinearGradientBrush(GetMainArea(), Color.DimGray, 
      Color.Black, LinearGradientMode.Vertical);
    System.Drawing.Drawing2D.LinearGradientBrush _StatusBrush = 
      new LinearGradientBrush(GetMainArea(), StatusColor1, 
      StatusColor2, LinearGradientMode.Vertical);
    System.Drawing.Drawing2D.LinearGradientBrush _MainBrush = 
      new LinearGradientBrush(GetMainArea(), FirstColor, 
      SecondColor, LinearGradientMode.Vertical);
    
    // Draw left
    if (LeftBarSize > 0)
    {
        _graphics.FillRoundedRectangle(_LeftAndRightBrush, 
          this.GetLeftArea(), this.RoundedCornerAngle, 
          RectangleEdgeFilter.TopLeft | RectangleEdgeFilter.BottomLeft);
        _graphics.DrawString(this.LeftText, this.Font, 
                  Brushes.White, this.GetLeftArea(), f);
    }
    
    // Draw status
    if (StatusBarSize > 0)
    {
        _graphics.FillRoundedRectangle(_StatusBrush, this.GetStatusArea(), 
             this.RoundedCornerAngle, RectangleEdgeFilter.None);
        _graphics.DrawString(this.StatusText, this.Font, 
             Brushes.White, this.GetStatusArea(), f);
    }

    // Draw main background
    _graphics.FillRoundedRectangle(Brushes.DimGray, 
        GetMainAreaBackground(), this.RoundedCornerAngle, 
        RectangleEdgeFilter.None);

    // Draw main
    _graphics.FillRoundedRectangle(_MainBrush, this.GetMainArea(), 
        this.RoundedCornerAngle, RectangleEdgeFilter.None);
    _graphics.DrawString(this.MainText, this.Font, 
        Brushes.White, this.GetMainAreaBackground(), f);

    // Draw right
    if (RightBarSize > 0)
    {
        _graphics.FillRoundedRectangle(_LeftAndRightBrush, 
           this.GetRightArea(), this.RoundedCornerAngle, 
           RectangleEdgeFilter.TopRight | RectangleEdgeFilter.BottomRight);
        _graphics.DrawString(this.RightText, this.Font, 
           Brushes.White, this.GetRightArea(), f);
    }

    // Clean up
    _LeftAndRightBrush.Dispose();
    _MainBrush.Dispose();
    _StatusBrush.Dispose();
}

Drag and Drop

To make this control a little more useful, I added drag and drop to it, controlled by AllowDrag. To enable the control to be "selected", I had to prevent the DoDragDrop() from triggering on MouseDown. This was solved by adding a small check so that you have to drag the control outside a small radius before it triggers. I also have _isDragging to be able to trigger DoDragDrop(); in the OnMouseMove override.

C#
protected override void OnMouseDown(MouseEventArgs e)
{
    this.Focus();
    base.OnMouseDown(e);
    _mX = e.X;
    _mY = e.Y;
    this._isDragging = false;
}

protected override void OnMouseMove(MouseEventArgs e)
{
    if (!_isDragging)
    {
        // This is a check to see if the mouse is moving while pressed.
        // Without this, the DragDrop is fired directly
        // when the control is clicked, now you have to drag a few pixels first.
        if (e.Button == MouseButtons.Left && 
                _DDradius > 0 && this.AllowDrag)
        {
            int num1 = _mX - e.X;
            int num2 = _mY - e.Y;
            if (((num1 * num1) + (num2 * num2)) > _DDradius)
            {
                DoDragDrop(this, DragDropEffects.All);
                _isDragging = true;
                return;
            }
        }
        base.OnMouseMove(e);
    }
}

protected override void OnMouseUp(MouseEventArgs e)
{
    _isDragging = false;
    base.OnMouseUp(e);
}

Selection

My purpose was to use this control in a FlowLayoutPanel and to make it look like a ListView, so I wanted to be able to "select" the items. This is made by changing the background color while focused. It's not totally perfect, but it was good enough for me.

C#
protected override void OnGotFocus(EventArgs e)
{
    this.BackColor = Color.SandyBrown;
    base.OnGotFocus(e);
}

protected override void OnLostFocus(EventArgs e)
{
    this.BackColor = Color.Transparent;
    base.OnLostFocus(e);
}

protected override void OnClick(EventArgs e)
{
    this.Focus();
    base.OnClick(e);
}

Final Thoughts

This little Christmas workshop actually turned out pretty good. I could probably add more functions and better looking graphics and animation, but since my customers don't want it or need it, I think I will leave it like this for the moment.

Check out my other article about the FlowLayoutPanel to see how I plan to use it.

History

  • 2009-12-21 - v1.0 - Article added.

License

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