Create complex custom server controls

by ion.robu 24. October 2008 09:51
 

Introduction

 

Each ASP.NET developer has need, at least once, during developing of one or more applications, to create custom functionalities and or design features, related to application purpose. Indeed ASP.NET provides a very wide category of these controls, covering most of functionalities and developers tastes. But what if a developer needs an extra functionality, which ASP.NET controls cannot offer it? Or maybe somebody else wants to be original and create own controls, to reuse them each time need?

An answer could be: create a custom control. ASP.NET provides a mechanism which allows creating custom controls, as the developer defines it. In this way, developer can define control structure, properties, its representation, giving a shape for an object that control represents.

Custom controls, as a server control, can have 2 representations: one, in aspx (or ascx) file from application, which describes the structure of the control, and other in code file (C#, VB etc.) which describes the functionality of the control. For example, a classic ASP.NET server control, Label, has associated a class in ASP.NET libraries, which defines the functionality of the control (members, properties, behavior such as rendering mode etc.) and a representation in aspx files from application, where the class is instantiated in object, as they are defined in that page:

<asp:label ID="lblTest" Runat="server" Text="This is a test label"></asp:label>

This control is instantiated at runtime in an object of type Label and will generate a HTML control:

<span id="lblTest">This is a test label</span>,

and sent to the web browser, which will display.

The question is: how is generated a Label server control? Or, generally, how is generated every asp.net control in one or more Html controls, to be sent to the web browser which made the request. Let’s consider, for example a DataGrid control: a complex ASP.NET control, with numerous modalities for defining data displaying, which will be rendered as a HTML table control, containing a lot of simple HTML controls, such as buttons, textboxes, images etc, calls of javascript code etc., depending of its defining structure from aspx files. How functions this rendering mechanism, from a control to another?

The aspx definition of an ASP.NET control is made in a markup language, having an xml structure. Thus, the control definition is a collection of tags. The root tag is the definition of the control itself, while the children tags will provide information about control properties, children controls and so on (control metadata).

Each ASP.NET control has associated a control builder class. This class describes the way the control will be rendered. When the control starts to be parsed, the control builder class provides the necessary information about how will be interpreted the content of the control. Each tag will be interpreted in a form or another, depending of tag type. For example, <asp:ListItem> tag of a <asp:DropDownList> control will generate a option for DropDownListControl:

           

            If (Tag = “ListItem”) Then

                        AddOptionToControl

 

Example

 

To take a closer look to this mechanism, let’ create a custom control, a very simple tree. Our control can be defined as a tree structure, and will print some text on the screen. (Obviously, our tree is almost useless in real applications; it will be defined here just to demonstrate how custom controls can be implemented).

 

Here is the markup language definition of the SampleTree control

 

&lt;st:SampleTree ID="stWorld" runat="server" Text="Earth"&gt;

      &lt;Level1 Name="Europe"&gt;

            &lt;Level2 text="France"&gt;&lt;/Level2&gt;

            &lt;Level2 text="Great Britain"&gt;&lt;/Level2&gt;

            &lt;Level2 text="Romania"&gt;&lt;/Level2&gt;

      &lt;/Level1&gt;

      &lt;Level1 Name="Africa"&gt;

            &lt;Level2 text="Senegal"&gt;&lt;/Level2&gt;

            &lt;Level2 text="Maroc"&gt;&lt;/Level2&gt;

            &lt;Level2 text="Somalia"&gt;&lt;/Level2&gt;

      &lt;/Level1&gt;

      &lt;Level1 Name="Asia"&gt;

            &lt;Level2 text="Japan"&gt;&lt;/Level2&gt;

            &lt;Level2 text="China"&gt;&lt;/Level2&gt;

      &lt;/Level1&gt;

&lt;/st:SampleTree&gt;

 

As can be seen the tree has two levels, and has a property, Name (for the first level) or Text (for second level). When will be rendered, tree will generate the output

 

 

Let see the control definition:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.ComponentModel;

using System.Collections;

 

namespace STControl

{

       public class SampleTreeControlBuilder : ControlBuilder

       {

              public override Type GetChildControlType(string tagName, IDictionary attributes)

              {

                     if (tagName.ToLower() == "level1")

                           return typeof(TreeLevel1);

                     return null;

              }

 

 

       }

 

 

       [ToolboxData("&lt;{0}:SampleTree runat=server&gt;&lt;/{0}:SampleTree&gt;")]

       [ControlBuilder(typeof(SampleTreeControlBuilder))]

       [ParseChildren(false)]

       public class SampleTree : WebControl

       {

              private List&lt;TreeLevel1&gt; level1Collection = new List&lt;TreeLevel1&gt;();

              public List&lt;TreeLevel1&gt; Level1Collection

              {

                     get

                     {

                           return level1Collection;

                     }

                     set

                     {

                           level1Collection = value;

                     }

              }

 

              private string text;

              public string Text

              {

                     get {

                           return text;

                     }

                     set {

                           text = value;

                     }

              }

             

              protected override void AddParsedSubObject(object obj)

              {

                     if (obj is TreeLevel1)

                           this.level1Collection.Add((TreeLevel1)obj);

              }

 

              protected override void CreateChildControls()

              {

                     this.Controls.Clear();

                     HtmlGenericControl divTree = new HtmlGenericControl("div");

                     Label lblTreeText = new Label();

                     lblTreeText.Text = this.Text;

                     divTree.Controls.Add(lblTreeText);

                     foreach (TreeLevel1 level1 in this.level1Collection)

                     {

                            HtmlGenericControl divGroup = new HtmlGenericControl("div");

                           Label lblGroup = new Label();

                           lblGroup.Text = "___" + level1.Name;

                           divGroup.Controls.Add(lblGroup);

                           foreach (TreeLevel2 level2 in level1.Level2Collection)

                           {

                                  HtmlGenericControl divProp = new HtmlGenericControl("div");

                                  Label lblProp = new Label();

                                  lblProp.Text = "______" + level2.Text.ToString();

                                  divProp.Controls.Add(lblProp);

                                  divGroup.Controls.Add(divProp);

                           }

 

                           divTree.Controls.Add(divGroup);

                     }

                     this.Controls.Add(divTree);

              }

 

 

       }

 

       public class TreeLevel1ControlBuilder : ControlBuilder

       {

              public override Type GetChildControlType(string tagName, IDictionary attributes)

              {

                     if (tagName.ToLower() == "level2")

                           return typeof(TreeLevel2);

                     return null;

              }

 

 

       }

 

       [ControlBuilder(typeof(TreeLevel1ControlBuilder))]

       [ParseChildren(false)]

       public class TreeLevel1 : WebControl

       {

              private List&lt;TreeLevel2&gt; level2Collection = new List&lt;TreeLevel2&gt;();

              public List&lt;TreeLevel2&gt; Level2Collection

              {

                     get

                     {

                           return level2Collection;

                     }

                     set

                     {

                           level2Collection = value;

                     }

              }

 

 

              private string name;

              public string Name

              {

                     get

                     {

                           return this.name;

                     }

                     set

                     {

                           this.name = value;

                     }

              }

 

              protected override void AddParsedSubObject(object obj)

              {

                     if (obj is TreeLevel2)

                           this.level2Collection.Add((TreeLevel2)obj);

              }

 

       }

 

       public enum LevelType

       {

              Text = 1,

              Collection = 2

       }

 

       public class TreeLevel2 : WebControl

       {

              private LevelType type;

              public LevelType Type

              {

                     get {

                           return this.type;

                     }

                     set {

                           this.type = value;

                     }

              }

 

              private string text;

              public string Text

              {

                     get

                     {

                           return this.text;

                     }

                     set

                     {

                           this.text = value;

                     }

              }

 

       }

 

      

}

 

The main class is SampleTree, which is derived by WebControl. Of course, our functionality is based on WebControl functionality; we just add or modify some things in some things in behavior of control generation. This class has following attributes:

[ToolboxData("&lt;{0}:SampleTree runat=server&gt;&lt;/{0}:SampleTree&gt;")]

[ControlBuilder(typeof(SampleTreeControlBuilder))]

[ParseChildren(false)]

 

ToolboxData specifies which text will be added in aspx files when control will be added by drag and drop from toolbar. This attribute has no role in control generation.

ParseChildren attribute, when true, specifies that child tags must be map with class properties. When false (as our case), this map will not be made; thus, a custom mapping occurs, provided by associated ControlBuilder class.

ControlBuilder is a very important attribute for customization of rendering control. It specifies name of the class which will be used to render the control structure. In this way we will override the default control builder class associated with WebControl class; this thing allows us to provide our tags and associate them with specific classes

In SampleTreeControlBuilder, delegated to define parsing rules, we have overridden method GetChildControlType. This method is automatically called for every child tag encountered in control definition and gets as parameter the name of the tag:

public override Type GetChildControlType(string tagName, IDictionary attributes)

{

       if (tagName.ToLower() == "level1")

              return typeof(TreeLevel1);

       return null;

}

 

In this function, we specified that, if name of the tag is “Level1”, return TreeLevel1 type, which is a class defined below in the code. In this way, we defined the rule which sais that child tag &gt;Level1&lt; will be associated with an object of type TreeLevel1. If any other child tag occurs in control definition, it will not be recognized and will be ignored.

What happened next? If the tag is a “known” one, will be added to a list in the class:

protected override void AddParsedSubObject(object obj)

{

       if (obj is TreeLevel1)

              this.level1Collection.Add((TreeLevel1)obj);

}

 

In this way we will parse all the tags and add them as children objects in parent control. This mechanism can be applied recursively, allows us to define as many levels as we want (in our control, Level2 is processed in a similar way).

In the end, all we have to do is to transform children objects in controls, for any level from control definition. We will do that overriding function CreateChildControls(), generating the control with expected content and purpose.

 

CustomTree.rar (23.74 kb)

Tags:

Comments

Comments are closed

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen

RecentComments

Comment RSS

Calendar

<<  September 2010  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar