A downloadable file for this article is available here.
The sample downloadable solution (zip) is entirely developed using Visual Studio.NET 2003 Enterprise Architect on Windows Server 2003 Standard Edition. But I am confident that it would work with other versions of Windows (which support .NET 1.1) as well.
Introduction
Everyone knows that ASP.NET already contains a flexible Button control for designing web applications with ease. You can even develop your own custom control using the existing Button control by deriving it from the “Button” class (available in ASP.NET framework). Of course, everyone knows this, too. But not everyone would know that we can totally design a Button control right from scratch, controlling the complete implementation.
In my previous articles, I already introduced the design and implementation of our own Custom Server Control from scratch. If you are quite new to the concept of Custom Controls in ASP.NET, I suggest you read my previous articles on this topic before going through this article.
Even though I could use System.Web.UI.Control as the base class for my button control, I selected System.Web.UI.WebControls.WebControl as the base class, because of the convenience and ease. Apart from all of these, we will also implement support for “postback” and “viewstate”. We will also examine client-side JavaScript being emitted from our control, which makes it cute looking!
The System.Web.UI.Control class features few rendering methods which could be overridden. This gives us less flexibility in developing the custom control, when we compare it with the rendering methods available in the System.Web.UI.WebControls.WebControl class. Of course, some of the most important properties (such as width, height, font, and so on) of the WebControl class get inherited to our textbox control, just as if they were granted.
Before talking too much about the control, let us go to the implementation first. We will create a Visual Studio.NET 2003 solution throughout this article with more than one project.
The default “Render” method (when not overridden by us) usually calls the “RenderBeginTag” before calling any other methods. The “RenderBeginTag” method in my control has been implemented something like this:
Public Overrides Sub RenderBeginTag(ByVal writer As System.Web.UI.HtmlTextWriter) AddAttributesToRender(writer) writer.RenderBeginTag(HtmlTextWriterTag.Table) writer.RenderBeginTag(HtmlTextWriterTag.Tr) writer.RenderBeginTag(HtmlTextWriterTag.Td) End Sub
Within the above method, I am trying to call the “AddAttributesToRender(Writer)” statement to add any attributes to my button control (in this case it is simply a TABLE tag). In fact, it is not a user defined method; it has been already defined within the “WebControl” class, and I am about to override it. Usually, the “AddAttributesToRender” method is initiated from within the “RenderBeginTag” method (that too as the first statement). I hope you can understand the second statement, which actually renders “<TABLE…..><TR><TD>” (with all the attributes emitted by “AddAttributesToRender” method) to the client (browser). Make a note that -- the tags have not yet been closed!
Coming to the next code fragment, I have an empty method as follows:
Protected Overrides Sub RenderContents(ByVal writer As System.Web.UI.HtmlTextWriter) writer.Write(Text) End Sub
The above method does nothing apart from displaying the content (or text in this case) following the previous set of tags. In this case, we don’t have any child controls. So, I don’t need to include “base.Render(writer)” as the last statement. Going further we have the following:
Public Overrides Sub RenderEndTag(ByVal writer As System.Web.UI.HtmlTextWriter) writer.RenderEndTag() 'closing td writer.RenderEndTag() 'closing tr writer.RenderEndTag() 'closing table End Sub
The above method simply contains three statements as “writer.RenderEndTag”. We already opened few tags using “writer.RenderBeginTag(…)” within the RenderBeginTag” method. We didn’t close them anywhere. And we do that using the above method. You need not even specify which tag to close. It could automatically understand (from our proper HTML hierarchy) which tag to close. That’s the beauty of ASP.NET custom control development.
In the previous section, we saw the methods implemented for rendering the control. But, we didn’t talk much about the “AddAttributesToRender” method. In this section, we will deal with it.
The “AddAttributesToRender” method is basically used (or overridden) to add HTML attributes and styles that need to be rendered for the respective custom control. Let me explain its implementation to you part by part (available in the downloadable solution).
If you observe carefully, you will see that within the above set of statements, the first statement is “base.AddAttributesToRender(writer)”. Is this necessary? Of course it is! It is always a good practice to have the statement “Base.AddAttributesToRender(Writer)” as the first statement within the “AddAttributesToRender” method. Never remove it (unless you have any reason to do so). There also exists one more secret behind it.
During the design time of VS.NET, you may move the control to your favorite location (or adjust its size, or change some of its properties, and so on). All of that information (say CSS) would be remembered and again written back to our control using this statement. If you forget this statement, your control works in a floating manner at the left-top of the web page (without having any design time attributes set).
The statement “writer.AddAttribute” is generally used to emit an attribute of a tag. You can use all existing attributes from “HtmlTextWriterAttribute” or simply provide your own string in quotations. The second statement (writer.AddStyleAttribute) is generally used to add style (or inline CSS) to a tag. You can use all existing styles from “HtmlTextWriterStyle” or simply provide your own string in quotations. I hope you can understand the rest. Proceeding further we have:
If Not Page.IsStartupScriptRegistered(Me.UniqueID & "_MouseOver") Then Page.RegisterStartupScript(Me.UniqueID & "_MouseOver", getJS4MouseOver(Me.UniqueID & "_MouseOver")) End If If Not Page.IsStartupScriptRegistered(Me.UniqueID & "_MouseOut") Then Page.RegisterStartupScript(Me.UniqueID & "_MouseOut", getJS4MouseOut (Me.UniqueID & "_MouseOut")) End If writer.AddAttribute("onMouseOver", Me.UniqueID & "_MouseOver();") writer.AddAttribute("onMouseOut", Me.UniqueID & "_MouseOut();")
The above statements actually register JavaScripts to the aspx page. I am always using “UniqueID” in the above statements because the JavaScript for each instance of this control should be different for each one. If we don’t use this trick, all controls would focus on the same JavaScript, resulting in very funny outputs! Okay. Let us proceed further.
The above statement is the most important (or even the heart) of all, as it emits the JavaScript for an “auto-postback” to the server when the control is clicked. Without the above statement, it would never behave like a button.
Another modification to the same control is the following code fragment. Look it over carefully.
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) If Not Page Is Nothing Then Page.VerifyRenderingInServerForm(Me) End If MyBase.Render(writer) End Sub
The above implementation of the “Render” method ensures that the placement of your control should be within the “<FORM>” tag itself. It would automatically raise an error if not placed within the “<FORM>” tag. You should also observe the second statement, “MyBase.Render(writer)”. It asks to proceed with rendering as usual, without considering any other issue.
The next question would be, how did I implement “OnClick” event within my control? The following statements at various locations made it possible:
Public Event Click As EventHandler Public Sub RaisePostBackEvent(ByVal eventArgument As String)Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent OnClick(EventArgs.Empty) End Sub
Protected Overridable Sub OnClick(ByVal e As EventArgs) RaiseEvent Click(Me, e) End Sub
It is the concept of both eventing and delegating together. The above statements made it possible to implement ‘postback’ing together with delegating. I suggest you to go through OOPS in .NET, if you are not familiar with Events and Delegates.
I implemented two more properties for “ImageOver” and “ImageOut” scenarios. The following shows a code fragment of one of them (the other is also quite similar):
<EditorAttribute(GetType(System.Web.UI.Design.UrlEditor), GetType(System.Drawing.Design.UITypeEditor))> _ Property [ImageOverURL] () As String Get Return viewstate("ImageOverURL") & "" End Get Set (ByVal Value As String) viewstate("ImageOverURL") = Value End Set End Property
You should be able to understand everything except the “<EditorAttribute..>”. That specification is used to provide an open dialog box to select files (of images) from within the properties window during the web page design. If you omit that, you need to provide the path of the file manually (without selection).
Coming to the JavaScripts issue, I included two methods, “getJS4MouseOver” and “getJS4MouseOut” to emit JavaScript for every instance of the control. The following would give you an idea of “getJS4MouseOver” (the other is also quite similar):
Private Function getJS4MouseOver(ByVal FunctionName As String) As String Dim js As String
I just kept on concatenating the JavaScript into a single variable, and finally returned that JavaScript back to the caller. I would suggest you use “StringBuilder” rather than simply concatenating it.
Remarks
This control has been developed just to initiate and show the power of custom control to control everything right from the beginning to the end, including client-side JavaScript emission. I didn’t quite implement all of the features of original ASP.NET Button control (as that would take a series of articles). You just need to learn a few more interfaces and methods to implement all of the original features along with your own features.
I leave it to the developers to further enhance the same control. The areas in which you can improve the control would include eventing, better JavaScript for validation, data-binding, and so on. Good luck.
Any comments, suggestions, bugs, errors, feedback etc. are highly appreciated at jag_chat@yahoo.com.