26998 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
ewheregoose
[b][url=http ://www.repli caomega.com/ ]replic<stro ng><a href="http:/ /www.replica omega.com/"> replica watch</a></s trong> <br> <strong><a href="http:/ /www.replica omega.com/"> replica watches</a>< /strong> <br >
ewheregoose
<strong><a href="http:/ /www.replica omega.com/"> replica omega</a></s trong> | <strong><a href="http:/ /www.replica omega.com/"> replica omega</a></s trong> | <strong><a href="http:/ /www.replica omega.com/"> fake omega</a></s trong> <br> <title>Omega constellat
ewheregoose
[b][url=http ://www.tourd umontblanc.n et/]mont blan<strong> <a href="http:/ /www.tourdum ontblanc.net /">mont blanc document marker</a></ strong> <br> <strong><a href="http:/ /www.tourdum ontblanc.net /">montblanc fineliner</a ></strong> < br>
ewheregoose
<strong><a href="http:/ /www.tourdum ontblanc.net /">mont blanc pens discount</a> </strong> | <strong><a href="http:/ /www.tourdum ontblanc.net /">mont blanc pen</a></str ong> | <strong><a href="http:/ /www.tourdum ontblanc.net /">fake mont blanc pen</a></str ong>
ewheregoose
<strong><a href="http:/ /www.timberl anddd.com/"> timberland outlet</a></ strong> <br> <strong><a href="http:/ /www.timberl anddd.com/"> timberland boots</a></s trong> <br> <strong><a href="http:/ /www.timberl anddd.com/"> timberland outlet</a></ strong> <br> <br>

Donate
Donate and help us fund new challenges
Donate!
Due Date: Sep 30
September Goal: $40.00
Gross: $0.00
Net Balance: $0.00
Left to go: $40.00
Contributors


News Feeds
The Register
Security rEsrchRs
find nu way 2 spot
TXT spam
SHARE "N" SINK:
OneDrive corrupting
Office 2013 files
Singapore slings
18k fine at
text-spam-spaffing
biz owner
Apple, FBI probe
nude celeb hacks
South Korea"s top
wireless carrier
builds Internet of
Eels
Microsoft changes
cert test
providers, hints at
fun new exams
Epiphany hits
Raspberry Pi
founders, users
Sex-in-space geckos
killed by frigidity
Transparency by
Telstra: a good
start but not
enough
Cisco climbs aboard
containerisation
cloudwagon, with
security
reservations
Slashdot
Finland"s Nuclear
Plant Start Delayed
Again
Ask Slashdot:
Linux-Friendly
Desktop x86
Motherboard
Manufacturers?
Hidden Obstacles
For Delivery Drones
Hackers Behind
Biggest-Ever
Password Theft
Begin Attacks
Tox, a Skype
Replacement Built
On "Privacy First"
Net Neutrality
Campaign To Show
What the Web Would
Be Like With a
"Slow Lane"
New Computer Model
Predicts Impact of
Yellowstone Volcano
Eruption
Raspberry Pi Gets a
Brand New Browser
Power Grids: The
Huge Battery Market
You Never Knew
Existed
Radioactive Wild
Boars Still Roaming
the Forests of
Germany
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 16763 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