Click here to Skip to main content
Click here to Skip to main content
Go to top

Progess Bar Control for ASP.NET

, 15 Feb 2005
Rate this:
Please Sign up or sign in to vote.
A reusable progress bar server control for ASP.NET server pages.

Sample Image - aspprogressbar.png

Introduction

This article describes the process to create a reusable progress bar that can be controlled at the server or client side.

Background

In web UI development, it is sometimes necessary to display resource usage information. Some applications for progress bars include displaying the amount of messages in a queue in relation to the queue size or displaying the number of current logged-in users in relation to the maximum users to date. The possibilities are endless. In my first attempt at creating an HTML based progress bar I used span elements in an ASP.NET server page to visually represent the progress bar. I then used an IFrame to embed that server page, where I wanted to display the progress bar. This approach had a severe drawback from very complicated span nesting in order to display the progress bar correctly and style interactions from the containing page. There were also accuracy problems in displaying the filled portion of the progress bar as well. There are also several issues with using an embedded IFrame element, some of them not trivial. I then decided to create a reusable ASP.NET server control to host the progress bar. The span elements have all the same drawbacks, however, so I then decided to find a better approach and began researching ASP.NET bar graph examples, which are very similar to a progress bar. I decided to go with a fairly robust solution using HTTP handlers and images rendered "on the fly". This is the approach I will describe in this article.

Using the code

I'll describe the following topics in this section:

  • Basic server control creation.
  • Progress bar server control properties.
  • Rendering the progress bar server control.
  • HTTP handler CustomImageHandler to handle images rendered "on the fly".
  • Using the progress bar on a server page.

Basic server control creation

Create a new class derived from System.Web.UI.WebControls.WebControl and add the using statements as shown below:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Design;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.IO;
using System.Math;

namespace YourNamespace
{
    public class ProgressBar : System.Web.UI.WebControls.WebControl
    {
    }
}

Next add the following attribute to the top of the class file (under the using statements).

[assembly: TagPrefix("YourNamespace", "ShortName")]

Replace YourNamespace and ShortName with values that make sense in your project. This attribute controls what the visual designer places inside the <@Register> tag on the server page when dragging the server control from the toolbox. It allows for a naming scheme that is more appropriate for your application. I'll cover this in more detail in the Using the progress bar section.

Add the following code to the top of the class declaration:

    /// <summary>
    /// This is the common graphical progress bar.
    /// </summary>
    /// <remarks>
    /// This custom control produces a graphical progress bar.
    /// </remarks>
    [DefaultProperty("Type")]
    [ToolboxData("<{0}:ProgressBar runat="server"></{0}:ProgressBar>")]
    [ToolboxBitmapAttribute( typeof(ProgressBar) ) ]
    public class ProgressBar : System.Web.UI.WebControls.WebControl
    {
        

Feel free to add/replace the comment code above the declaration to suit.

The DefaultProperty attribute declares the Type property to be the default property. This allows you to refer the property implicitly:

ProgressBar1 = BarType.Horizontal;

The ToolboxData attribute declares the text that the toolbox will add to the server page when dragged onto the page. The {0} portion of the attribute description is a replacement variable for the registered namespace of this control. This is controlled by the TagPrefix attribute described above. The following will be added by the visual designer when the control is dropped onto a server page:

<ShortName:ProgressBar id="ProgressBar1" runat="server"></ShortName:ProgressBar>

The ToolboxBitmapAttribute attribute describes the bitmap to be displayed inside the toolbox. If you provide just a class type, without a name, the visual designer will search the assembly for a 16x16 bitmap of that name. You can create a 16x16 bitmap and add it to the project as an embedded resource in order to represent your control, otherwise the visual designer will use a default bitmap.

You now have a basic control, that will be rendered in the toolbox and will populate your server page correctly when dragged from the toolbox. I will cover how to add this control to your toolbox in the Using the progress bar section. In the next section I will cover adding properties to the progress bar control.

Progress bar server control properties

The following properties have been identified as necessary for basic progress bar rendering: Type, Height, Width, FillColor, BackColor, Border, BarSize, FillPercent, and Format.

The Type property is an enumeration that is designated as follows:

public enum BarType
{
    Horizontal,
    Vertical
}

The properties Height, Width, and BarSize are all Unit value types and describe the height, width, and internal size of the progress bar respectively. FillPercent is a float value which specifies how much of the bar to display. The properties BackColor and FillColor are unsigned long values which specify the background color and bar/fill color respectively.

The property Format is an enumeration value that is designated as follows:

public enum BarFormat
{
    Gif,
    Jpeg,
    Png,
    Bmp
}

Here's the code for those properties:

#region Private member variables
BarFormat format;
BarType type;
bool border;
System.Drawing.Color fillColor;
double fillPercent;
Unit barSize;
#endregion

#region Public properties
[DefaultValue(BarFormat.Png), Category("ProgressBar Common"),
                    Description("Compression format for image.")]
public BarFormat Format
{
    get { return format; }
    set { format = value; }
}

[DefaultValue(false), Category("ProgressBar Common"), 
                      Description("Flag to show border or not.")]
public bool Border
{
    get { return border; }
    set { border = value; }
}

[DefaultValue(BarType.Horizontal), Category("ProgressBar Common"),
                   Description("Type of progress bar to render.")]
public BarType Type
{
    get { return type; }
    set { type = value; }
}

[Category("ProgressBar Common"), 
                Description("Background color of progress bar.")]
public System.Drawing.Color FillColor
{
    get { return fillColor; }
    set { fillColor = value; }
}

[DefaultValue(0.0), Category("ProgressBar Common"),
                 Description("Fill percentage of progress bar.")]
public double FillPercent
{
    get { return fillPercent; }
    set { fillPercent = value; }
}

[DefaultValue(0.0), Category("ProgressBar Common"),
                Description("Size of progress bar inside frame")]
public Unit BarSize
{
    get { return barSize; }
    set { barSize = value; }
}

[Browsable(false)]
public string ContentType
{
    get { return "image/" + Format.ToString().ToLower(); }
}

The last property ContentType is declared as non-browsable using the Browsable(false) attribute. This property is used by the HTTP handler class described in the section HTTP handler CustomImageHandler.

Rendering the progress bar server control

There are two modes for rendering an ASP.NET server control. The first, Design mode, provides a method for the control to represent itself in the Visual Designer. Since the progress bar control consists of an image rendered on the fly an accurate representation cannot be specified so a default representation can be used:

//Initial request for ProgressBar object
protected override void Render(HtmlTextWriter output)
{
    if (this.Site != null && this.Site.DesignMode )
    {
        // Be careful to specify only html that can be embedded in 
        //any tag.  This will prevent problems when displaying in the 
        //Visual Designer.
        output.Write(string.Format("<font size='1'  
               color='SeaGreen' face='arial'> [ProgressBar::{0}]
               </font><br>", ID) );
    }

Rendering the control at runtime requires an introduction to the next topic - using an HTTP handler to handle displaying the images rendered on the fly. This handler will intercept requests for a certain webpage, which doesn't exist, but is specified within an <img> tag generated by the progress control. I will go into detail about this handler in the next section. The code to generate the <img> tag is shown below:

else
{
    string uniqueName = GenerateUniqueName();

    Page.Application[ProgressBarRenderStream.ImageNamePrefix + uniqueName] 
                                            = this;

    string toolTip = string.Format( "{0}",
        Enabled ? ((ToolTip == null || ToolTip == string.Empty) ?
        Round(FillPercent, 2).ToString()+"%" : ToolTip) : "");

    //Write relative URL for image stream request
    output.Write(
        string.Format("<img src='{0}?id={1}' border='{2}' height='{3}'
                    width='{4}' alt='{5}'>",
                    ProgressBarRenderStream.ImageHandlerRequestFilename,
                    uniqueName, (this.Border ? "1" : "0"),
                    (int)this.Height.Value, (int)this.Width.Value,
                    toolTip );
}

//Generates a new name for this control & registers
string GenerateUniqueName()
{
    string sControlName = System.Guid.NewGuid().ToString();

    //Identifies requested ProgressBar image
    return sControlName;
}

This code will generate a unique name, which will identify the progress bar class currently rendering the output. This unique name is used as the src attribute in an <img> tag to allow the HTTP handler to find the class later. The values ProgressBarRenderStream.ImageNamePrefix and ProgressBarRenderStream.ImageHandlerRequestFilename are declared in the HTTP handler class and will be described in the next section. This code should generate an HTML sequence similar to the following:

<img src='image_stream.aspx?id=89f7f4ed-7433-460d-ae97-53c22aa2a232' 
                            border='1' height='8' width='128' alt='25.5%'>

The values will be different depending on property values and the GUID value generated.

This is still not enough to render the control. The following functions will take care of drawing the progress bar and rendering it to a memory stream:

public MemoryStream RenderProgressBar()
{
    try
    {
        if( Site != null && Site.DesignMode )
        {
            string sType = this.Type.ToString();
            return null;
        }
        else
        {
            return makeProgressBar();
        }
    }
    catch
    {
        return null;
    }
}

private MemoryStream makeProgressBar()
{
    // now convert percentage to width (subtract 2 
    //from width to adjust for border)
    Bitmap bmp = new Bitmap((int)Width.Value, 
              (int)Height.Value, PixelFormat.Format32bppArgb);
    MemoryStream memStream = new MemoryStream();

    Brush fillBrush = new SolidBrush( FillColor );
    Brush backgroundBrush = new SolidBrush( this.BackColor );

    // draw background
    System.Drawing.Graphics graphics = Graphics.FromImage( bmp );
    graphics.FillRectangle(backgroundBrush, 0, 0, 
                        (int)Width.Value, (int)Height.Value);

    double fillAmount;
    if( this.Type == BarType.Horizontal )
    {
        // draw a horizontal bar
        // draw only BarSize height, centered vertically
        // inside the frame
        fillAmount = Width.Value * (FillPercent/100.0);
        graphics.FillRectangle(fillBrush, 0,
                ((int)Height.Value - (int)BarSize.Value)/2,
                (int)fillAmount, (int)BarSize.Value);
    }
    else
    {
        // draw a vertical bar
        // draw only BarSize width, centered horizontally
        // inside the frame
        fillAmount = Height.Value * (FillPercent/100.0);
        graphics.FillRectangle(fillBrush, ((int)Width.Value - 
                (int)BarSize.Value)/2,
                (int)Height.Value-(int)fillAmount,
                (int)BarSize.Value, (int)Height.Value);
    }

    graphics.Save();

    System.Drawing.Imaging.ImageFormat imgformat = 
               System.Drawing.Imaging.ImageFormat.Png;
    switch( Format )
    {
        case BarFormat.Bmp:
            imgformat = ImageFormat.Bmp;
            break;

        case BarFormat.Gif:
            imgformat = ImageFormat.Gif;
            break;

        case BarFormat.Jpeg:
            imgformat = ImageFormat.Jpeg;
            break;

        case BarFormat.Png:
            imgformat = ImageFormat.Png;
            break;
    }

    // Render BitMap Stream Back To Client
    bmp.Save(memStream, imgformat);
    return memStream;
}

This code will be called by the ProgresBarRenderStream class which is described in the next section.

Rendering images on the fly using an HTTP handler

In order to render images on the fly, it is necessary to intercept the HTTP request and look for a specific web page, which does not exist. This web page name is specified by the progress bar server control during rendering. The HTTP handler will find that value from the query string after it detects the "special" web page. The handler can then use this value to look up the requested progress bar control for rendering on the fly.

There are two steps necessary to provide a custom HTTP handler. First create a class derived from IHttpModule. This code is shown below:

public class ProgressBarRenderStream : IHttpModule
{
    public const string ImageHandlerRequestFilename="image_stream.aspx";
    public const string ImageNamePrefix="i_m_g";

    public ProgressBarRenderStream()
    {
    }

    public virtual void Init( HttpApplication httpApp )
    {
        httpApp.BeginRequest += new EventHandler(httpApp_BeginRequest);
    }

    public virtual void Dispose()
    {
    }

    private void httpApp_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication httpApp = (HttpApplication)sender;

        ProgressBar pb = null;
        if( httpApp.Request.Path.ToLower().IndexOf(
                               ImageHandlerRequestFilename) != -1 )
        {
            pb = (ProgressBar)httpApp.Application[ImageNamePrefix + 
                        (string)httpApp.Request.QueryString["id"]];
            if( pb == null )
            {
                return; // 404 will be returned
            }
            else
            {
                try
                {
                    System.IO.MemoryStream memStream = pb.RenderProgressBar();
                    memStream.WriteTo(httpApp.Context.Response.OutputStream);
                    memStream.Close();

                    httpApp.Context.ClearError();
                    httpApp.Context.Response.ContentType = pb.ContentType;
                    httpApp.Response.StatusCode = 200;
                    httpApp.Application.Remove(ImageNamePrefix + 
                                   (string)httpApp.Request.QueryString["id"]);
                    httpApp.Response.End();
                }
                catch(Exception ex)
                {
                    ex = ex;
                }
            }
        }
    }
}

The code above illustrates the shared values that the ProgressBar control uses when rendering the <img> tag. These values are then used in the httpApp_BeginRequest function which is registered in the Init function:

httpApp.BeginRequest += new EventHandler(httpApp_BeginRequest);

The final step required is to add the HTTP handler to your web config file. This must be added to your root level web config file in order to work properly.

<httpModules><add name="ProgressBarRenderStream" 
type="YourNamespace.ProgressBarRenderStream,YourAssemblyName" /></httpModules>

Using the progress bar on a server page

Using the progress bar on a server page is easy. First add a reference to the control in your project file. Next add the following directive to the top of your server page:

<%@ Register TagPrefix="ShortName" Namespace="YourNamespace" 
                                   Assembly="YourAssemblyName" %>

Finally, add the control to your page and change the parameters to suit:

<ShortName:ProgressBar  id="ProgressBar1" 
                     runat="server"></ShortName:ProgressBar>

Conclusion

I started my research thinking there was an easy solution to my problem - to provide direct feedback of a process using a graphical representation. Digging further I realized that, what I was trying to do was very difficult for a server control to do by itself without compromising security on the site. Further research led me to the HTTP handler method of dealing with images rendered on the fly. This method is powerful and can be extended to other applications, limited only by your imagination.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Scott McCain
Web Developer
United States United States
I am a software engineer with over 20 years experience. My interests include web application development, graphics programming, network connectivity, and game programming.

Comments and Discussions

 
QuestionBut how to make it really dynamic? Pinmemberyitian5-Nov-05 10:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 15 Feb 2005
Article Copyright 2005 by Scott McCain
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid