As the title of the article says, we will attempt to explain the steps for
creating a web control, and in particluar an up-down control. The
functionality
of this control is the same as MFC's Up-Down control and Windows Forms' System.Windows.Forms.NumericUpDown
control. We will try to explain the steps in as much as details as we can. A
lot of information is available in .NET framework documentation. It is just
a question of how
to apply that information to create a control that can be useful for your
day to day operations on the web pages.
There are two ways that you use to build your server control.
-
Composite Control
-
Custom Control
We will not go into details of defining these two types. There is some good
information available in the .NET framework documentation about it. Stating it
simply, a composite control is a server control that utilizes one or
more existing server controls, and a Custom Control is very much
like implementing your Windows controls where you will handle the drawing by
providing an implementation of the OnPaint
event. Similarly for Custom
Controls, you will provide your own rendering of the control in the Render
event handler. We will discuss these details later in the article.
Why did we create a custom control?
If you look at the Up-Down control, it is made up of one textbox and two
buttons to increment or decrement the value. That means this can be
accomplished using a composite control made up of one asp:TextBox
and two asp:ImageButton
controls. But we decided to create a custom
control and do our won rendering of HTML
tags.
We had couple of reason to go this route. Although the ASP.NET documentation states
that the server controls can generate a browser compatible HTML
tags,
in our real experience we have observed that for browsers other than Internet
Explorer, the HTML code generation has been very
inconsistent. The other reason is that the control value will be changed with
every click on spinner controls. That means we will have to manage the state on
client side instead of doing post backs for every click as that is not a very
efficient solution because it generates a lot of network traffic. So we will
have to emit client side JavaScript for managing state.
Lets get started
Define control
Every server control is derived from Control
or WebControl
.
So the first step is creating a control class that derives from one of these
classes. WebControl
itself is derived from the Control
class.
The advantage of deriving your control's class from WebControl</u> is that along with the common
properties of <code>Control
, your control will inherit the
implementation of very commonly used properties of web controls e.g. Width
,
Height
, Font
etc. So we decided to take advantage of
it and derive our control from the WebControl
class.
[ToolboxData("<{0}:UpDownControl Runat="server">/>")]
[Designer(typeof(UpDownControl))]
public class UpDownControl : WebControl, IPostBackDataHandler
You should have observed that we have specified the the control will provide
implementation of the IPostBackDataHandler
interface. The reason for
doing is that our control will be saving the value that user has specified by
incrementing or decrementing through spinners and these value has to be conveyed
to the page when the Form
is posted back for processing. The IPostBackDataHandler
interface provides two methods, LoadPostData
and RaisePostDataChangedEvent
,
that our control will implement to handle the post back events from the
containing page.
What properties to define?
Now lets see what all custom properties that we will need other than the
standard ones like Color
, Font
, etc. Since our
control is an Up-Down control, that means we require some graphics to display
our up and down arrows. So we have two properties that can be used to set/get
URL s for the up-down arrows.
[Category("Text")]
[Description("URL for up arrow image")]
public string UpArrowImgUrl
{
get{return this.m_strUpArrowImg;}
set{this.m_strUpArrowImg = value;}
}
Then we need to provide some means of controlling on which side of the text
box the up-down arrows should be displayed. It will be either left or right.
So we need to define a property that will get/set this value. Instead of just
using int
type, we decided to use an enum
so that this
value can be set descriptively, meaning instead of setting the value as 0 or 1,
we will set it by "Left" and "Right" values.
public enum ArrowsPosition
{
Left = 0,
Right = 1,
}
[Category("Appearance")]
[Description("Specify if up-down arows will be displayed on right side or not.")]
public ArrowsPosition ArrowsPositionSide
{
get{return this.m_enumArrowsPosition;}
set{this.m_enumArrowsPosition = value;}
}
Next we need to specify how far the arrows will be placed from the text box.
This is like providing spacing between two HTML objects. So we defined the
property ArrowsSpacing
that we can set/get.
[Category("Appearance")]
[Description("Specify the spacing between text-box and up-down arrows.")]
public int ArrowsSpacing
{
get{return this.m_iArrowsSpacing;}
set{this.m_iArrowsSpacing = value;}
}
Then the properties that affect the values that are saved in the text box. Like
System.Windows.Forms.NumericUpdown
we have specified properties that
control min-max and increment values. For more details, look at the source
control provided with the article.
Rendering of control
To render HTML tags for your custom server control it is very essential that
you provide and implementation of the Render</u> method in your control class. This method
provides a chance to the control to render its contents or what ever it wants to
emit to display to the browser. <code>Render
method has only one
parameter, HtmlTextWriter
, which acts as an output stream to write
the HTML contents to the web page.
protected override void Render(HtmlTextWriter output)
{
}
Therefore in this method we render all the HTML tags to display the text box
and up-down arrow images. We emit input
and img
for
rendering this user interface, and for displaying the right kind of image, we
get the value from the UpArrowImgUrl
and DownArrowImgUrl
properties.
This rendering has been implemented in two private methods, AddTextSpanSection
and AddImageSection
, of the control class.
strRender.Append("\n<table cellspacing=\"0px\" cellpadding=\"0px\"");
strRender.Append(" height=\"");
strRender.Append(this.Height + "\">");
strRender.Append("\n<tr>");
strRender.Append("\n<td valign=\"bottom\">");
strRender.Append("<img border=0 title=\"Increment value\" src=\"");
strRender.Append(this.m_strUpArrowImg);
strRender.Append("\"");
strRender.Append(" onclick=\"javascript:" + this.UpArrowClickFunction + "();");
strRender.Append("\">");
strRender.Append("\n</td>");
strRender.Append("\n</tr>");
Handle events
Rendering is just one part of implementing a server control. It is like providing
a body to a human being. Now this thing needs a brain too, and in the world of
web page controls, the brain power is to react to various actions that happen
to it. We are implementing an up-down spinner control; that means the control
has to react to user's clicks on the up/down arrows. We provide this ability
by implementing the onclick
event of each img
tag that
displays arrows. If you look at the code above, we call the UpArrowClickFunction
during the rendering of the img
control. This private method returns
the name
of the client side javascript
function that handled click event on
the up-arrow image control. Using the same method we add client-side event handlers for
the down-arrow image control's click event.
strRender.Append("<img border=0 title=\"Decrement value\" src=\"");
strRender.Append(this.m_strDownArrowImg);
strRender.Append("\"");
strRender.Append(" onclick=\"javascript:" + this.DownArrowClickFunction + "();");
strRender.Append("\">");
Managing state of the control
The Up-down control has no way of raising post back events. This is not
totally true, but we don't want this control to do post-backs for every click on the
arrow images. This means we will have manage state using some client-side
scripts. But first we need some placeholder where we can save the state. We
use the old trick of using a hidden input control on the page. This
input will be used to convey the value stored in the text box to the page when
user submits the form or any other control fires a post-back event.
The ASP.NET framework helps in accomplishing this task. There is RegisterHiddenField
method on the Page
class that automatically registers a hidden field
on the form. And when the page is rendered, this hidden field is rendered on
the page too. We call this method in OnPreRender</u> event handler. The <code>RegisterHiddenField
method has two arguments, one is used to specify the unique ID for the hidden
control and the second argument is used to store the initial value for the
control.
protected override void OnPreRender(EventArgs args)
{
base.OnPreRender(args);
if (Page != null)
{
Page.RegisterHiddenField(HelperID, Text);
}
}
In the control implementation, we have provided a private property, HiddenID
,
which uses ClientID
property of the control to generate a unique
ID for the hidden variable.
How is state communicated to page?
OK, we have rendered the control, handled the click events on the arrows and managed
the state. Now the last thing that is left, how the change in control's
state will be communicated, and if the control wants to raise any event if its
state has changed how will that happen? Here are the steps that need to be
followed to wire this all up.
-
The first step is to tell the containing page that our control needs to be
notified when post-back happens. In the .NET framework, the callbacks are
accomplished through events and delegates. The
Page
class has a method
RegisterRequiresPostBack
that needs to be called and handed a reference
to our control. This method call registers our control with Page
for
receiving events.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (Page != null)
{
Page.RegisterRequiresPostBack(this);
}
}
-
After registering the control with
Page
, now we need to
figure out what are we going to do with the event. And most important,
how this all happens. At the start of the article when the control was defined, we mentioned
that control class will implement the IPostBackDataHandler
interface.
When the Page
raises post-back event for the control, it calls
the LoadPostData
method on the control. This method has two parameters. The send parameter
contains the collection of all the values that have posted back, so we
can get the posted
value from this collection. And then you can compare the posted value with the
current value to check if the value has changed or not. If the value has
changed, return true
from this method indicating that the state has
changed. This will raise change event on the control.
bool IPostBackDataHandler.LoadPostData(string postDataKey,
NameValueCollection postCollection)
{
string presentValue = this.Text;
string postedValue = postCollection[HiddenID];
if (!presentValue.Equals(postedValue))
{
this.Text = postedValue;
return true;
}
return false;
}
-
If
LoadPostData
method returns true
, the Page
will call RaisePostDataChangedEvent
on the control giving it a
chance to raise its own event or do what ever processing it needs to do.
How to use this control on your page?
In the page where you want to use this control, the first step is to register
it at the top of the page, using @Register
directive.
<%@ Register TagPrefix="Softomatix"
Namespace="ASPNet_Controls"
Assembly="ASPNet_Controls"
%>
And then include in it the page just like other server controls, specifying the
property values that you want to assign to the control.
<form id="UpDownControlClient" method="post" runat="server">
<Softomatix:UpDownControl
Runat="server"
Height="30"
UpArrowImgUrl="images\UpArrow_10x10.gif"
DownArrowImgUrl="images\DownArrow_10x10.gif"
ArrowsPositionSide="Right"
ArrowsSpacing="2"
Text="2"
MaxValue="10"
MinValue="0"
Increment="1"
id="UpDownControl1">
</Softomatix:UpDownControl>
<asp:Button Runat="server" ID="wndSubmit" Text="Submit">
</asp:Button>
</form>
What next?
In this article we have shown the implementation of the control. Through out
the code you will observe that we have used attributes on properties and on
the class
definition. After you have created the control, you would like to include it in
the tool box so that you can just use it by dragging from the tool box on to
page. In the next article we will extend the implementation so that this
control can be included in the server control tool box.
Contact Us
If you any questions or suggestions, you can post the comments in this article or
directly contact us at softomatix@pardesiservices.com
or visit us at Softomatix.