Click here to Skip to main content
Click here to Skip to main content

Reusable Progress Bar Server Control

By , 15 Feb 2005
 

Sample Image

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. 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 reference 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, DesignMode, 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'>&nbsp;[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 (the values will be different depending on property values and the GUID value generated):

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

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

About the Author

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

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionsome questions ?memberdongxiang11 Jun '07 - 21:46 

i download ur codde and append into my project but i connt see anything ,just one picture there,bar is not moving .
 
so please soulve the question
 
doneydongxiang@gmail.com
 
doney doneydongxiang@gmail.com
QuestionHow to install progressbar? [modified]membermeksShiv28 Sep '06 - 18:26 
Hai
I need to use this progressbar in my project for upload a file.I need to show the status of progress while uploading a file.I want to know how to use this in my project and how to install this control.please give solution as early as possible
 

Thanks in advance
MekalaasriSmile | :)
 

-- modified at 5:15 Saturday 30th September, 2006
 

 

 
-- modified at 5:28 Saturday 30th September, 2006
GeneralThere is stil errors in the samplememberpnr1 Mar '05 - 18:17 
Just downloade the democode to try this progressbar, but the code isn't working!
GeneralRe: There is stil errors in the samplesussAnonymous4 Mar '05 - 7:41 
I haven't had time to update the sample so, I will be removing it as it seems most readers of this article have a fairly solid understanding of developing ASP.Net server pages using custom controls. There are also plenty of demos out there on how to use a server control. Hopefully that should eliminate any more issues with the sample code as my goal is to provide a starting point for people, perhaps give them some ideas to expand upon.
GeneralA Suggestion &amp; other commentsmemberjyjohnson28 Feb '05 - 3:48 
Scott -- This is great article. However, it wasn't until the end that realized this control does not (out of the box, at least) support "near-time" updating (e.g., a long-running task's % completion). So perhaps if you were to rename this to "Reusable Status Bar Server Control" other folks would not misunderstand the purpose. The word "progress" generally represents the ability to auto-update (near-time or real-time) the status of a task.
 
Also, there are plenty of examples of using threads in ASP.net to display a % completion status bar -- I'm not sure what you're trying to say in your post about threads. And a meta-tag refresh is not the only way to do that. For example, you could use Javascript + web-service "behaviors". Or, for Mozilla-based browsers (IE, Safari, Opera) you can use XMLHttpRequest which enables client-side initiated server requests without using post-back. {Google uses this for Google "Suggests" and "GMail".}
 
Finally, this might be a stretch but I think most readers understand the basic web/client architecture and stateless-ness.
 

GeneralRe: A Suggestion &amp; other commentsmemberScott McCain1 Mar '05 - 14:44 
I think your suggestion is excellent and will, hopefully, deter further discussion of "near-time" updating progress bars, which is a completely different subject than I am trying to present. I realize that a client can handle "near-time" progress bars, but without a postback-response mechanism there is really no way of knowing how long a task is taking at the server, so the best you get is a "fake" progress bar or, maybe, an "educated guess" as to the progress based on some previously measured metric. Can you explain to me how a server can, in the same postback-response, update the progress bar and force a client to update the screen? Is there an HTTP command, unlike GET/POST that tells the client to wait on the socket for more information? I'm speaking at the actual response level on the socket, as I have written web clients for mobile devices, and it always comes down to a GET/POST and then read the response. You can use multiple-threads, but that doesn't help the server get you the response quicker.
QuestionHow about a legitimate sample?sussbrady gaster16 Feb '05 - 10:02 
Luckilly, I knew how to register HttpModules with the web.config - your sample totally forgot this. Likewise, your namespaces don't match. The demo code doesn't work with the sample source download - AT ALL.
 
PLEASE clean up your code, make it work, come up with a justifiable piece of FUNCTIONING code and then upload it for the world to use. This is a GREAT idea, and I would LOVE to use your control in a lot of apps that I'm currently working on.
 
Advice -
 
Come up with a sample that WORKS (out of the box, in such a way that a user has ZERO code to write on their own - otherwise, what's the point?). Then, do a sample that uses something like (oh I dunno) a Thread.Sleep(1000) call a few times (say for 5 seconds) to actually show the progress bar "sliding across the page."
 
What you have right now is a VERY complex line-drawing GDI+ example. Big whoop. Give me a progress bar and I'll give you a backflip.
 
brady gaster
professional geek
AnswerRe: How about a legitimate sample?memberScott McCain16 Feb '05 - 10:25 
Brady (or professional geek),
Are you aware of what section the example is in? It is in the ASP.NET controls section. This means you can't use something like Thread.Sleep(1000) to test it, there are no threads in http request. Http is a stateless protocol so how could you have threads anyway? At any rate, it is impossible to give a "working" example as you stated since it is a WEB application. You will have to understand how IIS works and how to create a web application from the MMC snapin. There is more to this than just creating a GDI+ image, which would be nothing, I agree, but try to find some code that does something like hotmail when it displays the progress bar of how much your mailbox is full. That was my intent, not to provide a Thread.Sleep(1000) example to show a progress bar filling. If that's the case, just add a refresh meta tag to the web page and save the state. Then increment the state each time the page is refreshed. Viola, you have your progressing progress bar.
 
You do bring up a valid point about the source and demo's however, and I will be updating those within the next couple of days to remove the invalid project reference as well as providing a more cohesive example. Thanks for the great feedback!
GeneralRe: How about a legitimate sample?membermdissel17 Feb '05 - 8:25 
Scott McCain wrote:
If that's the case, just add a refresh meta tag to the web page and save the state. Then increment the state each time the page is refreshed. Viola, you have your progressing progress bar.
 
And what about a scenario in a webpage that does a lot of (long during) things? You can't just use the meta-tag to refresh the page that's also doing a lot of work (like starting a bunch of queries, all with different running-time..
 
Thanks
Marco
GeneralRe: How about a legitimate sample?memberScott McCain17 Feb '05 - 11:25 
mdissel wrote:
And what about a scenario in a webpage that does a lot of (long during) things? You can't just use the meta-tag to refresh the page that's also doing a lot of work (like starting a bunch of queries, all with different running-time..
 
That would be an IE progress bar, something that this article does not cover. An application for this would be to display to the user a bar that shows usage, for example, of a mail box. This is similar to what Hotmail does when you check your inbox by displaying a percentage bar indicating how full your inbox is. Another example would be a charity drive site that wanted to show how much of a certain target was reached. I did not intend, nor is it possible, to provide a real-time display of a progress. That would require you to write your own web client (you rewrite IE) utilizing a progress bar to measure a long action, or you replace IE's progress bar with your own. Remember HTTP is a stateless protocol so once the request is made the socket is not kept open for real time updates. Basically a socket is opened, the request is made (usually on port 80), the response is processeed by the client. The HTTP protocol itself has not concept of state, it's up to the client and server to maintain and communicate state VIA the HTTP protocol. So it would be impossible to show, in real time, the progress of a long running query without a refresh meta tag since the socket is not kept open after the initial request which, I assume, will set the progress to 0. What you will get is nothing until the request if finished (you exit the post back code or you do a Response.End). This means that at best you will only see a full progress bar since that is when the request will complete.

GeneralRe: How about a legitimate sample?memberamaansworld20 May '11 - 0:52 
I agree with brandy. The code is useless if it is not properly managed. Although the control is superb to use in web world but still needs lot of correction and work to do. Online demo is useless if it doesn't work on your computer. Developer need nothing to write in code but here lot of effort need to do to run code successfully.
THanks
GeneralStormCommunications dll missingmembertchhailo16 Feb '05 - 10:01 
Hi
 
just wanted to check out your demo, but stormwebcontols include is missing.
 
thanks, Alex.
GeneralRe: StormCommunications dll missingmemberScott McCain16 Feb '05 - 10:19 
Hi,
Yes, that is an old reference to the original web control library that contains other controls that I authored. You can safely remove the reference and reference the ProgressBarControl project instead. That should remove any dependency problems. I will be pushing an update to the demo and source code soon due to everyones great feedback. Thanks again!
 
Regards,
Scott
GeneralRe: StormCommunications dll missingmemberferchuz10 Apr '06 - 4:42 
I download ProgressBarControl source code, and use the dll generated...
but i cant see anything when i run the web project...
 
<%@ Register TagPrefix="bar" Namespace="ProgressBarControl" Assembly="ProgressBarControl" %>
 
<bar:ProgressBar id="ProgressBar1" runat="server">
 
anything else?
 

 
ferchuz
GeneralRe: StormCommunications dll missingmemberferchuz10 Apr '06 - 4:44 
sorry i forgot...
<bar:ProgressBar id="ProgressBar1" runat="server">
 
and then?

 
ferchuz
GeneralRe: StormCommunications dll missingmemberferchuz10 Apr '06 - 5:05 
no problem...
i needed config the properties heigh and width...
 
ferchuz

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 16 Feb 2005
Article Copyright 2005 by Scott McCain
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid