26992 total geeks with 3514 solutions
Recent challengers:
 Welcome, you are an anonymous user! [register] [login] Get a yourname@osix.net email address 

Articles

GEEK

User's box
Username:
Password:

Forgot password?
New account

Shoutbox
MaxMouse
It's Friday... That's good enough for me!
CodeX
non stop lolz here but thats soon to end thanks to uni, surely the rest of the world is going good?
stabat
how things are going guys? Here... boring...
CodeX
I must be going wrong on the password lengths then, as long as it was done on ECB
MaxMouse
lol... the key is in hex (MD5: of the string "doit" without the "'s) and is in lower case. Maybe i should have submitted this as a challenge!

Donate
Donate and help us fund new challenges
Donate!
Due Date: Aug 31
August Goal: $40.00
Gross: $0.00
Net Balance: $0.00
Left to go: $40.00
Contributors


News Feeds
The Register
Researchers
camouflage haxxor
traps with fake
application traffic
Chipzilla gives
birth to a TINY
comms chip
AWS Zocalo squares
up to box Box,
Dropbox, sync "n"
share flocks
MEN WANTED to
satisfy town full
of yearning
BRAZILIAN HOTNESS
Netflix releases
home-grown DDOS
dectectors
Microsoft boots
1,500 dodgy apps
from Windows Store
Cisco teases UCS
refresh with most
of 2014"s best
buzzwords
NBN predicts a
million premises
next year
Neutrinos lay bare
solar proton
reactions
Oz fed police in
PDF redaction SNAFU
Slashdot
Fake NVIDIA
Graphics Cards Show
Up In Germany
NASA Telescopes
Uncover Early
Construction of
Giant Galaxy
Fish Raised On Land
Give Clues To How
Early Animals Left
the Seas
Netflix Open
Sources Internal
Threat Monitoring
Tools
Old Doesn"t Have To
Mean Ugly:
Squeezing Better
Graphics From
Classic Consoles
$33 Firefox Phone
Launched In India
New NRC Rule
Supports Indefinite
Storage of Nuclear
Waste
CenturyLink:
Comcast Is Trying
To Prevent
Competition In Its
Territories
Underground
Experiment Confirms
Fusion Powers the
Sun
Ask Slashdot: What
To Do About
Repeated Internet
Overbilling?
Article viewer

Custom controls Part II : Enhanced ProgressBar



Written by:sfabriz
Published by:thinkt4nk
Published on:2006-10-12 09:35:31
Topic:Dot.Net
Search OSI about Dot.Net.More articles by sfabriz.
 viewed 16703 times send this article printer friendly

Digg this!
    Rate this article :
This time is about enhancing the System.Windows.Forms.ProgressBar .Net control.

A couple of years ago, while I was writing a java application called MyFTP which was part of a test for the university, I liked to place on the form (or should I say JFrame) I was building a progress bar. It has been a matter of 5 seconds to discover that calling setStringPainted(true) on a java progress bar gives you the ability to write the percentage text over the control.
When I made my first try to do the same on dotnet ProgressBar, I sadly discovered that you can't paint the percentage value over the control, as easily as you can do it in java. I made many tries to do it, but with no luck, until I got some knowledge about custom controls.
This article hence is about enhancing a ProgressBar in order to be able to paint the percentage text over the control and also render the control using a custom color or a custom gradient made by two colors.

To achieve the desired result we need to:

  • Inherit from System.Windows.Forms.ProgressBar
  • Override the OnPaint method and setup the control to use it
  • Override some properties to apply our changes
  • Shade some properties that we don't want to appear in design time

The explanation will be straightforward and you don't need to be confident with concepts like Control Designers or so, since we're still talking quite easy here.

Ok, first of all, fire up your VS05 environment, create a C# class library project and a windows application project that will test our custom control. It's better if you place them both into just one solution, because doing so, when you build your custom controls, they appear on the toolbox along with the standard controls and components, making their usage very easy.

Ok, let's see the code a bunch of lines at a time. Here it is the declaration:

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing.Drawing2D;

namespace YourCompany.Controls

public class ZProgressBar : System.Windows.Forms.ProgressBar {

        /// <summary>
        /// Constructor
        /// </summary>
        public ZProgressBar() {


            // setting the style in order to get
            // double buffering, userpaint and redraw on resizinig
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);


        }
        
        ...

As you can see we inherit from ProgressBar. The constructor does just one thing, it sets some flags to true. We want double buffering (AllPaintingInWmPaint & OptimizedDoubleBuffer), we want the control to be drawn using the OnPaint method (UserPaint) and finally we also provide the ability for the control to call an invalidation when resized (ResizeRedraw).

Let's check the properties now, and then the methods.

        // gradient top color if in gradient mode, top color otherwise
        private Color gradientTop=Color.White;

        public Color GradientTop {
            get { return gradientTop; }
            set {
                gradientTop = value;
                this.gradientColor= ZPBGradientColors.Custom;
                this.Invalidate();
            }
        }

        
        // gradient bottom color if in gradient mode
        private Color gradientBottom=Color.Black;

        public Color GradientBottom {
            get { return gradientBottom; }
            set {
                gradientBottom = value;
                this.gradientColor= ZPBGradientColors.Custom;
                this.Invalidate();
            }
        }

        
        private ZPBGradientColors gradientColor= ZPBGradientColors.Custom;

        [Description("Sets a predefinite gradient color. If set to custom then GradientTop and GradientBottom can be set by the user")]
        public ZPBGradientColors GradientColor {
            get { return gradientColor; }
            set {
                gradientColor = value;
                SetGradientColors(value);
                this.Invalidate();
            }
        }

This section is about gradients. The ZProgressBar has basically 2 colors. If the GradientColor property (which is an enum) is set to Simple the color that will be used to paint the foreground is gradientTop. If it is set to Gradient, the gradientTop and gradientBottom colors are used to paint the foreground, with a gradient made by theirselves. Finally, if the GradientColor property is set to Blocks, our ZProgressBar will be rendered like any other standard progressbar, with those green blocks you surely have already seen somewhere.
Note the [Description] attributes that provide some text for the desing-time environment. Also note that, when you set one of the gradient colors, the GradientColor property is set to Custom and, on the opposite, when you set the GradientColor to something different from Custom, the 2 gradient colors are set accordingly.
Here we go with the enumerations I chose, so you can understand how to handle those properties:

// gradient colors enumeration
public enum ZPBGradientColors {
        Custom, // default
        Red,
        Blue,
        Green,
        Yellow,
        Silver
    }
    
// ZProgressBar styles
public enum ZProgressBarStyles {
        Simple, // default, one color
        Gradient, // 2 colors
        Blocks // green default blocks
    }
    
// enum for the text if you decide to use it
public enum ZPBTextAlignment {
        Left,
        Center, // default
        Right
    }

Let's see the other properties:

        // style of the progressbar
        private ZProgressBarStyles style=ZProgressBarStyles.Simple;

        [Description("The ZProgressBar style. Continuous means single color, gradient two vertically combined colors and blocks means the usual green block default ProgressBar's rendering")]
        public new ZProgressBarStyles Style {
            get { return this.style; }
            set {
                this.style=value;
                if (value == ZProgressBarStyles.Blocks || value == ZProgressBarStyles.Simple) {
                    this.gradientColor= ZPBGradientColors.Custom;
                    gradientBottom=Color.Empty;
                }
                this.Invalidate();
            }
        }

        // whether or not the progressbar will display the value
        private bool showText=false;

        [Description("If true the progressbar will draw its value over itself")]
        public bool ShowText {
            get { return this.showText; }
            set {
                this.showText=value;
                this.Invalidate();
            }
        }

        [Browsable(true)]
        [Description("The font used to render the value")]
        public new Font Font {
            get { return base.Font; }
            set { base.Font = value; }
        }

        private Color fontColor=Color.Black;

        [Description("The color used to render the value text")]
        public Color FontColor {
            get { return this.fontColor; }
            set {
                this.fontColor=value;
                this.Invalidate();
            }
        }

        // new Value to force an invalidation
        public new int Value {
            get { return base.Value; }
            set {
                base.Value=value;
                this.Invalidate();
            }
        }

        private ZPBTextAlignment textAlignment=ZPBTextAlignment.Center;

        [Description("Sets where to draw the value is ShowText is true")]
        public ZPBTextAlignment TextAlignment {
            get { return textAlignment; }
            set {
                textAlignment = value;
                this.Invalidate();
            }
        }

When you set the Style property in desing-time, the code sets the gradient color accordingly. You don't need any of them if you're using blocks, and you'll need only the top one if you are going to use the single mode style. The ShowText property perfectly describes itself, as the Font one, but I want you to notice that [Browsable(true)] before the Font declaration. This enables the design-time environment to provide access to the font property via the property window, which is very handy and quick. Notice also that in most cases I call the Invalidate method to force a repaint of the control, since I want it to be consistent with the settings I make via the property window.

I also wanted to "shade" a couple of properties that I'm never going to use:

        // shaded values
        [Browsable(false)]
        public override RightToLeft RightToLeft {
            get { return base.RightToLeft; }
            set { base.RightToLeft = value; }
        }

        [Browsable(false)]
        public new int MarqueeAnimationSpeed {
            get { return base.MarqueeAnimationSpeed; }
            set { base.MarqueeAnimationSpeed=value; }
        }

A simple shading is made applying the [Browsable(false)] attribute before the overriding of the property or its declaration.

Let's check the methods (there are only 2 methods apart from the contructor):

        /// <summary>
        /// Helper method
        /// </summary>
        /// <param name="gradient">The gradient to be used</param>
        private void SetGradientColors(ZPBGradientColors gradient) {
            switch (gradient) {
                case ZPBGradientColors.Blue:
                    this.gradientTop=Color.AliceBlue;
                    this.gradientBottom=Color.DarkBlue;
                    break;
                case ZPBGradientColors.Green:
                    this.gradientTop=Color.LightGreen;
                    this.gradientBottom=Color.DarkGreen;
                    break;
                case ZPBGradientColors.Red:
                    this.gradientTop=Color.MistyRose;
                    this.gradientBottom=Color.DarkRed;
                    break;
                case ZPBGradientColors.Silver:
                    this.gradientTop=Color.WhiteSmoke;
                    this.gradientBottom=Color.Black;
                    break;
                case ZPBGradientColors.Yellow:
                    this.gradientTop=Color.LightYellow;
                    this.gradientBottom=Color.DarkOrange;
                    break;
            }
        }

This is a helper method. When you set the gradient to one of the 5 different gradients I provided (other than the Custom one), the 2 gradient colors are automatically set by this method. A quick way to set up a gradient.

And now, the core of the class' code; the OnPaint method:

protected override void OnPaint(PaintEventArgs e) {
            // call to the base class OnPaint method
            base.OnPaint(e);

            // custom painting here
            Graphics g=e.Graphics;

            g.SmoothingMode= SmoothingMode.HighSpeed;

            int valueLen=this.Value-this.Minimum;
            valueLen*=(this.Size.Width-2*margin);
            valueLen/=(this.Maximum-this.Minimum);

            // foreground rectangle
            Rectangle rect=new Rectangle(this.ClientRectangle.X+margin, this.ClientRectangle.Y+margin, valueLen, this.ClientRectangle.Height-2*margin);

            // background
            // if visualstyles are applied then use the ProgressBarRenderer
            // otherwise leave the background set by the BackColor property
            if (Application.RenderWithVisualStyles) {
                ProgressBarRenderer.DrawHorizontalBar(g, this.ClientRectangle);
            }

            // foreground
            switch (this.Style) {
                case ZProgressBarStyles.Simple:
                    using (SolidBrush foreBrush=new SolidBrush(this.gradientTop)) {
                        g.FillRectangle(foreBrush, rect);
                    }
                    break;
                case ZProgressBarStyles.Gradient:
                    using (LinearGradientBrush foreBrush=new LinearGradientBrush(this.ClientRectangle, gradientTop, gradientBottom, 90f)) {
                        foreBrush.SetBlendTriangularShape(this.triangleShape);
                        g.FillRectangle(foreBrush, rect);
                    }
                    break;
                case ZProgressBarStyles.Blocks:
                    if (Application.RenderWithVisualStyles) {
                        ProgressBarRenderer.DrawHorizontalChunks(g, rect);
                    } else {
                        using (SolidBrush foreBrush=new SolidBrush(this.gradientTop)) {
                            g.FillRectangle(foreBrush, rect);
                        }
                    }
                    break;
            }


            if (this.showText) {
                using (Brush fontBrush=new SolidBrush(this.fontColor)) {
                    using (StringFormat sf=new StringFormat()) {
                        switch (textAlignment) {
                            case ZPBTextAlignment.Center:
                                sf.Alignment=StringAlignment.Center;
                                break;
                            case ZPBTextAlignment.Left:
                                sf.Alignment=StringAlignment.Near;
                                break;
                            case ZPBTextAlignment.Right:
                                sf.Alignment=StringAlignment.Far;
                                break;
                        }
                        sf.LineAlignment=StringAlignment.Center;
                        g.DrawString(string.Format("{0}%", this.Value), this.Font, fontBrush, this.ClientRectangle, sf);
                    }
                }
            }
            
        }

First we call the base.OnPaint method, letting the control to do some eventual stuff we don't want to handle. After this we have to paint the background, the foreground, and eventually some text. At first I set the Graphics.SmoothingMode to HighSpeed, since I need the control to repaint fast. It's not a Picasso painting so I don't care about AntiAliasing stuff.
To draw the backgroung I just used the ProgressBarRenderer.DrawHorizontalBar method, which draws a rounded edges rectangle with white filling. If we cannot render the ZProgressBar using visual styles (like with WinXP) the BackColor will be the background of the control. (And similarly for the foreground, the gradientTop color will be used to render a single color)

Then we have to paint the foreground, and we do it accordingly to our Style setting. A switch clause is more than enough here. Notice that I used using(...) {...} in order to release some resources as soon as possible.
Also notice that I used the LinearGradientBrush.SetBlendTriangularShape method to adjust the 2 colors relative position.
The last thing about the foreground is that, if the Style property is set to Blocks, we call the ProgressBarRenderer.DrawHorizontalChunks method that perfectly does the job as on a regular ProgressBar.
The valueLen int property is the percentage value, calculated like this:
valueLen = [(val-min)/(max-min)]*(control's length)

If the ShowText property is set to true we also write a percentage text.
We set the brush color to the fontColor property I created and then we set the StringFormat.Alignment accordingly to the textAlignment property of our control: left, center or right, which translates in Near, Center and Far of the StringAlignment enumeration.

That's it. Compile the code and drag a ZProgressBar onto your brand new form. Set the properties as you like and fire it up. It's ready to go. It should work good in almost every situation, but bear in mind that I wrote this class just to be able to write this article and therefore it hasn't been tested at 100%.

I have made a small form with some ZProgressBar controls onto it. It is bundled in this zip file, along with the source code files.

Enjoy it and live in peace.
sfabriz

Did you like this article? There are hundreds more.

Comments:
Anonymous
2008-09-23 21:17:45
margin is not defined anywhere in the code. What is that for? Also, teh zip download is 0 bytes.
Anonymous
2008-10-29 19:28:31
define margin as an int like so:

private int margin = 2;

A value of 2 was about right for the look that I wanted for my bar. This margin represents the space between the outer rim of the control and where the color of the bar is actually drawn inside of its boundaries.

I also had trouble with the triangleShape call in the SetBlendTriangularShape() method inside of the OnPaint method which I could not find much information on in the msdn.

I did however look up the actual SetBlendTriangleShape() method and it accepts 1 or 2 parameters both float. Here are the descriptions:

focus
[in] Real number that specifies the position of the ending color. This number is a percentage of the distance between the boundary lines and must be in the range from 0.0 through 1.0.
scale
[in] Optional. Real number that specifies the percentage of the gradient's ending color that gets blended, at the focus position, with the gradient's starting color. This number must be in the range from 0.0 through 1.0. The default value is 1.0, which specifies that the ending color is at full intensity.

I used .2f and .6f, but you can play with the values to get the right effect.
Anonymous
2009-01-21 13:51:19
Apparently, the ZIP file download is still 0 bytes. Can you (re)upload a copy of the source please?
Anonymously add a comment: (or register here)
(registration is really fast and we send you no spam)
BB Code is enabled.
Captcha Number:


Blogs: (People who have posted blogs on this subject..)
bb
ASP.NET RadioButton GroupName when inside a Repeater on Sun 10th Jun 8am
I was thankful on finding this nugget of code, which makes the groupname work out when slamming in radiobuttons in an asp.net repeater. http://www.codeguru.com/csharp/csharp/cs _controls/custom/article.php/c12371/


     
Your Ad Here
 
Copyright Open Source Institute, 2006