Custom controls : Enhanced ProgressBar in vb.net

Started by dhilipkumar, Dec 15, 2008, 06:42 PM

Previous topic - Next topic

dhilipkumar


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

he 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();
            }
        }

dhilipkumar

Continuation  from last post


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;
            }
        }




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.


dhilipkumar

A light introduction to custom control creation (C#).

Several books have been written about custom controls.
The reason is because it is a tricky argument. The creation of a custom control can be either simple or pretty hard and require a lot of work. You have to pay attention to some very important details. The best thing to understand how it works is to see a light example, that shows you the right direction to get the desired result without being too trivial or too difficult.

So, I made a piece of software some time ago and I wanted to give my customers the possibility to change some textboxes background color. Those textboxes were colored because they were required fields. Coloring the background in this way, the user that is filling a form immediately knows what fields cannot be skipped. I always get frustrated when I present a complete software (or website) to someone, usually after months of hard work, and I realize that the only thing that matters to the customer is that the background color is not the one he likes most. You can write 20000 lines of code, but if you write in red what the customer wants to be written in blue, you will have an unsatisfied customer. That's how it works.
So now I give my customers the possibility to customize some colors in my applications.

The purpose of this article is not to use the ColorDialog class (which is a classic color picker), but to show the colors along their .net names in a ComboBox. As you know, ComboBoxes usually show just text strings, and so we must inherit from the ComboBox class to make a new class, which we'll call ColoredComboBox.

Let's start with the class declaration code:


public class ColoredComboBox : System.Windows.Forms.ComboBox

With this line we tell the framework that our class is inheriting from a standard ComboBox class. Now, we want this ColoredComboBox to behave exactly as a regular one, but with some differences when it comes to paint its items.
What we do is to add some properties and override the OnDrawItem() method to control the drawing operation ourselves.

First of all, the list of properties and the 2 constructors:

protected int inMargin;
        protected int boxWidth;
        protected bool useTransparent;
        protected bool useSystemColors;
        private Color c;

        public ColoredComboBox()
            :this(false,false,Color.Empty)
        {
             
        }

        public ColoredComboBox(bool useTransparent, bool useSystemColors, Color selected) {
            this.DrawMode=DrawMode.OwnerDrawFixed;
            this.DropDownStyle=ComboBoxStyle.DropDownList;
            this.inMargin=2;
            this.boxWidth=4;
            this.useTransparent=useTransparent;
            this.useSystemColors=useSystemColors;
            this.BeginUpdate();
            InitCombo();   
            this.EndUpdate();
            if (selected==Color.Empty) {
                base.SelectedIndex=0;
            } else {
                base.SelectedIndex=base.FindString(selected.Name);
            }
        }

I have added 2 constructors because in general I don't want transparent color and system colors to be added in the list, but I also wanted to have the possibility to use them in the future. As you can see the five properties are just some margin, width, two flags and a color. Their purpose is to properly init the combo and correctly draw the items.

Let's see the constructor code (the second one obviously): first of all we must set DrawMode to OwnerDrawFixed to tell the compiler that all the items are drawn manually and they are of the same size.
Then, since I just want a list of colors with no interaction with the user but the possibility to select one of them, I set the DropDownStyle to DropDownList. This makes the combo to just draw the items without letting the user to write into it.
I set the margins and width and whether or not to use system and transparent colors.
Then it comes the interesting part: with BeginUpdate() I tell the framework that I am updating the combo, and since I want that to be done as fast as possible, it hasn't to be repainted after each item's addition. I init the combo with the InitCombo() method and, after the update is completed, I tell the framework that now it can repaint correctly the combo, with the method EndUpdate().

The last 5 lines meaning is to select either the first item, or the color that has been passed to the constructor as the selected one.



dhilipkumar

Continuation  from last post

Let's dig into the core of the class:

private void InitCombo() {
    Color cc;
    foreach (KnownColor kc in Enum.GetValues(typeof(KnownColor))) {
        cc=Color.FromKnownColor(kc);
        if (!this.useTransparent && cc.Name==Color.Transparent.Name) continue;
        if (!this.useSystemColors && cc.IsSystemColor) continue;
        this.Items.Add(cc.Name);   
    }
}


This is the initialization method. Foreach Color the system recognizes as known, I add it to the combo's items. Before adding them I check if they are system colors or the transparent one and I choose to whether to add them or not accordingly with the contructor parameters I have been given.
As you can see I add the Color.Name property, which is just a string with the name of the color. After this method has completed its execution, I have my ComboBox items list complete and ready to be used.

How it's been used is up to the OnDrawItem() method, which comes below:

protected override void OnDrawItem(DrawItemEventArgs e) {
    base.OnDrawItem (e);
     
    if ((e.State & DrawItemState.ComboBoxEdit)!=DrawItemState.ComboBoxEdit) e.DrawBackground();
             
    c = Color.FromName((string)base.Items[e.Index]);
             
    Graphics g=e.Graphics;

    // the color rectangle
    g.FillRectangle(new SolidBrush(c),e.Bounds.X+this.inMargin,e.Bounds.Y+this.inMargin,
    e.Bounds.Width/this.boxWidth-2*this.inMargin,e.Bounds.Height-2*this.inMargin);

    // black border retangle
    g.DrawRectangle(Pens.Black,e.Bounds.X+this.inMargin,e.Bounds.Y+this.inMargin,
    e.Bounds.Width/this.boxWidth-2*this.inMargin,e.Bounds.Height-2*this.inMargin);

    // the color name
    g.DrawString(c.Name,e.Font,new SolidBrush(ForeColor),
    (float)(e.Bounds.Width/this.boxWidth+5*this.inMargin),(float)e.Bounds.Y);
}


This is the most important method in the class. First of all, since we don't want to rewrite all the code this method handles, we recall the base.OnDrawItem() method. This ensures that each part of the ComboBox is drawn properly. Then we add our custom code. We want to draw a rectangle (with a black border) on the left, and write the color's name on its right. In this way, we will show the user a list of items where each one is a colored rectangle with the color's name on its right.

We start with this line:
if ((e.State & DrawItemState.ComboBoxEdit)!=DrawItemState.ComboBoxEdit) e.DrawBackground();

that makes the framework to draw a standard background color when the combo is opened. It's the blue color that follows the mouse pointer when you move up and down onto the list of items.
After that we want a Color instance and we reconstruct it by getting the item string and using the Color.FromName() method.
Once we have the item color we can draw the rectangle, using a brush of that color, with some margins and width that are pretty easy to understand. We do this using the Gaphics.FillRectangle() method.

After the colored rectangle has been drawn, we draw a nice black border onto its bounds (stylish...), calling the Gaphics.DrawRectangle() method. And finally we draw the color name calling the Graphics.drawString() method, and we are done.

Now we have a ComboBox that shows colors along their names. We can include this class in our project and we can put an instance of it using this code (placed within the form's constructor):

// adding a colored combo box
this.SuspendLayout();

ccb=new ColoredComboBox();
ccb.Size=new Size(200,25);
ccb.Location=new Point(12,10);
this.Controls.Add(ccb);

this.ResumeLayout();


ccb has been declared as private ColoredComboBox, obviously outside the constructor.


thiruvasagamani

i have some doubts in your code
please explain the code dilip
Thiruvasakamani Karnan


dhilipkumar



just tel me what's u r doubt.....

im here clear your doubt.....

which part u cant understand from this article........... first u need to tr this code , if u got any error, tel me ,

i try to clear it....

thanks for u r reply