Contents
In this article, I'd like to show you how to build a custom update progress control.
I prepared the live demo here. Just click on the link to see the result or look at the following picture:

Pic 1
As you can see, there is the yellow box approximately in the middle of the page. The rest of the page is covered by a shadow and does not allow the users to click the buttons.
The article is divided into two parts. The first "Example of Use" and the second "Theory".
Consider the situation where you are using an UpdatePanel control for a partial rendering in your page. If you want to use the UpdateProgress control described in this article, then you have to follow these steps:
- Add a reference to the library DotNetSources.Web.UI.dll.
- Add a link to the CSS styles into your page like:
<link href="UpdateProgress.css" rel="stylesheet" type="text/css" />
Register the control. Into the web.config file section pages/controls, add the following line:
<add tagprefix="dns"
namespace="DotNetSources.Web.UI"
assembly="DotNetSources.Web.UI" />
Define the control in your page:
<dns:customupdateprogress runat="server"
id="progress" progressimage="ajax-loader2.gif" text="Updating..." />
And that's it. I think it's very simple, and the result will be as in Pic 1. This example is available in the source code related with this article and the name of the project is CustomUpdateProgressExample. Just open the project using Visual Studio, run it, and enjoy the demo.
Let's do the steps to create a control like that and describe some of the theory.
The first step is to create the control itself. Let's call it CustomUpdateProgress and derive from WebControl and IScriptControl. The control will have the following properties:
Delay - How long to wait before displaying the progress bar. In milliseconds.
ProgressImage - The path to the GIF displaying the progress.
Text - The text displayed during the progress.
Add these values as properties of the control like follows:
private long delay = 3000;
public long Delay {
get {
return delay;
}
set {
delay = value;
}
}
private string progressImage;
public string ProgressImage {
get {
return progressImage;
}
set {
progressImage = value;
}
}
private string text = "Updating...";
public string Text {
get {
return text;
}
set {
text = value;
}
}
The control itself will be rendered as DIV element. That is easy to specify in the base constructor like:
public CustomUpdateProgress()
: base(HtmlTextWriterTag.Div) {
}
The important part of the control is creating the child controls. Let's think about the layout of the control and stuff needed inside. Look at picture 2 below to understand the base elements inside. Basically there are two DIV elements. One is covering the page for disabling users to click buttons and another DIV as popup.

Pic 2
Let’s discuss the layout inside the popup. See picture 3 for understanding. It's no big deal, just three DIV elements sorted vertically.

Pic 3
Let’s see how to create DIVs like that dynamically in C#. The green panel from picture 3:
pnlMain = new Panel();
pnlMain.ID = "pnlMain";
pnlMain.CssClass = "uProg_modalPopup";
pnlMain.Style.Add("display", "none");
Controls.Add(pnlMain);
Red DIV from picture 3:
pnlMessage = new Panel();
pnlMessage.ID = "pnlMessage";
pnlMessage.CssClass = "uProg_messageDiv";
lblMessage = new Label();
pnlMessage.Controls.Add(lblMessage);
pnlMain.Controls.Add(pnlMessage);
Green DIV from picture 3:
pnlProgress = new Panel();
pnlProgress.ID = "pnlProgress";
pnlProgress.CssClass = "uProg_progressDiv";
image = new Image();
pnlProgress.Controls.Add(image);
pnlMain.Controls.Add(pnlProgress);
Orange DIV from picture 3:
pnlAbort = new Panel();
pnlAbort.ID = "pnlAbort";
pnlAbort.CssClass = "uProg_abortDiv";
cmdAbort = new Button();
cmdAbort.UseSubmitBehavior = false;
cmdAbort.Text = "Abort";
cmdAbort.OnClientClick =
string.Format("$find('{0}').Abort();return false;", ClientID);
pnlAbort.Controls.Add(cmdAbort);
pnlMain.Controls.Add(pnlAbort);
Take a notice of the JavaScript related to the button click event. The first step is to call:
$find('[component_name]').Abort();
I will discuss it later when I will describe the JavaScript part of this control. The second step is to call:
return false;
Why? Because we don't want to have this button as Submit and ASP.NET Button control always generates postback call as the last part of the button client click event. return false; stops the execution immediately after our code is executed and the postback won't be performed.
Finally create the background DIV from picture 2:
pnlBackground = new Panel();
pnlBackground.ID = "pnlBackground";
pnlBackground.CssClass = "uProg_modalBackground";
pnlBackground.Style.Add("visibility", "hidden");
Controls.Add(pnlBackground);
The introduced content must be created in the Init phase of the page life cycle. So, I override the OnInit method and I put the code there.
Afterwards I need to implement IScriptControl interface. There are two methods: GetScriptDescriptor and GetScriptReferences. In GetScriptDescriptors, you should specify the values you want to give to the JavaScript part. I will need the Delay for working with the interval in the JavaScript and I will also need the ID of the background DIV and ID of popup DIV. So the GetScriptDescriptors will look like follows:
public IEnumerable<ScriptDescriptor> GetScriptDescriptors() {
ScriptControlDescriptor descriptor =
new ScriptControlDescriptor("DotNetSources.Progress", this.ClientID);
descriptor.AddProperty("delay", this.Delay);
descriptor.AddProperty("backgroundId",
this.pnlBackground.UniqueID.Replace('$', '_'));
descriptor.AddProperty("popupId", this.pnlPopup.UniqueID.Replace('$', '_'));
return new ScriptDescriptor[] { descriptor };
}
The second method named GetScriptReference provides the JavaScript resources related to this control. I decided to release JavaScript as an embedded resource. The implementation can be like:
public IEnumerable<ScriptReference> GetScriptReferences() {
ScriptReference jsReference = new ScriptReference();
jsReference.Assembly = "DotNetSources.Web.UI";
jsReference.Name = "DotNetSources.Web.UI.scripts.DotNetSources_Progress.js";
return new ScriptReference[] { jsReference };
}
The server control is created. Now I need to create the JavaScript mirror of this control.
First, register the namespace:
Type.registerNamespace("DotNetSources");
Second, define the constructor:
DotNetSources.Progress = function(element) {
DotNetSources.Progress.initializeBase(this, [element]);
this._delay = null;
this._backgroundId = null;
this._popupId = null;
this._requestIsInProgress = 0;
this._timeOutId = null;
this._beginRequestDelegate = null;
this._endRequestDelegate = null;
}
As you can see there is the one parameter: element. It's the ID of the tag in DOM. Remember about the server control the part I specified that the server control will be rendered as DIV tag. So, this input element is ID of this DIV tag. What's happened next in the constructor? The properties are initialized to null and the delegates are initialized to null.
Furthermore, I'm going to specify the prototype:
DotNetSources.Progress.prototype = {
}
Inside the prototype, I define the methods and properties of the JavaScript class.
Let's see the properties:
get_delay: function() {
return this._delay;
},
set_delay: function(value) {
this._delay = value;
},
get_backgroundId: function() {
return this._backgroundId;
},
set_backgroundId: function(value) {
this._backgroundId = value;
},
get_popupId: function() {
return this._popupId;
},
set_popupId: function(value) {
this._popupId = value;
},
These properties are related to properties created in GetScriptDescriptors method in the server control. The ASP.NET AJAX extensions itself will take care of initializing these properties to the right values from the server control. So this is the way the handover of values between the server control and the JavaScript works.
When the JavaScript object is created by Microsoft AJAX extension, the initialize method is called. In the initialize method, I register the events like this:
initialize: function() {
DotNetSources.Progress.callBaseMethod(this, 'initialize');
this.registerEvents();
},
registerEvents: function() {
var manager = Sys.WebForms.PageRequestManager.getInstance();
this._beginRequestDelegate = Function.createDelegate(this, this.Show);
this._endRequestDelegate = Function.createDelegate(this, this.Hide);
manager.add_beginRequest(this._beginRequestDelegate);
manager.add_endRequest(this._endRequestDelegate);
},
Let's look at the code inside the registerEvents method. The first line...
var manager = Sys.WebForms.PageRequestManager.getInstance();
... is returning the instance of PageRequestManager. This is the key object in this control because using the PageRequestManager I can get the information that the asynchronous postback had begun and of course when it's finished. The next step is creating delegates using Function.createDelegate. It will also work if I put this.Show directly into the manager.add_beginRequest. However in a case like that, I will be missing the context of the right object. See that the current instance is needed in the Function.createDelegate call.
Now I will implement the public interface for the JavaScript class. It's about four methods:
Show - to display the progress, counting with delay
ShowInternal - to display the progress, without taking care of delay. Usually called by timer which is set in Show
Hide - to hide the progress
Abort - to cancel the asynchronous request and hide the progress
Let's code:
Show: function() {
this._requestIsInProgress = 1;
this._timeOutId = setTimeout
(Function.createDelegate(this, this.ShowInternal), this._delay);
},
ShowInternal: function() {
if (this._requestIsInProgress == 1) {
$get(this._backgroundId).style.visibility = 'visible';
$get(this._popupId).style.visibility = 'visible';
}
},
Hide: function() {
this._requestIsInProgress = 0;
clearInterval(this._timeOutId);
$get(this._backgroundId).style.visibility = 'hidden';
$get(this._popupId).style.visibility = 'hidden';
},
Abort: function() {
clearInterval(this._timeOutId);
var manager = Sys.WebForms.PageRequestManager.getInstance();
if (manager.get_isInAsyncPostBack()) {
manager.abortPostBack();
}
this.Hide();
},
Afterwards, it is good to implement the dispose method to free resources.
dispose : function() {
clearInterval(this._timeOutId);
DotNetSources.Progress.callBaseMethod(this, 'dispose');
}
Finally I derive this class from Sys.UI.Control:
DotNetSources.Progress.registerClass
('DotNetSources.Progress', Sys.UI.Control);
Thank you for reading this article and I hope it was useful.
- 31 July 2009
- 13 September 2009
- Code changes
- JavaScript class derived from
Sys.UI.Control
- The server control is implementing the
IScriptControl interface
- Text changes
- The link to the live demo added