Saturday, September 12, 2009

ASP.NET Composite Controls with Inner Controls

A Composite Control is simply a WebControl that contains one or more child controls, in this case the child controls are added in the mark up.
So lets starting with the main control.

First, in order to get VS support you have to add some attributes to the class, namely ToolboxData and DefaultProperty. This lets VS know how to parse the markup and tells VS to parse the MenuItem property for child controls making your class looks something like this:
   1: [DefaultProperty("MenuItems"), ParseChildren(true, "MenuItems")]
   2: [ToolboxData("<{0}:MenuControl runat=\"server\"></{0}:MenuControl>")]
   3: public class MenuControl : WebControl
Next you have to add the category attributes to the MenuItems property. When you do this you also need to set the DesignerSerializationVisibility and PersistenceMode options. This tells VS how to work with a collection of controls and you get something like this:
   1: [Category("Behavior"),Description("The item collection"), 
   2:     DesignerSerializationVisibility (DesignerSerializationVisibility.Content), 
   3:     PersistenceMode(PersistenceMode.InnerDefaultProperty)]
   4: public MenuItmeControls MenuItems {get; set;}
Next we override the Render method
   1: protected override void Render(HtmlTextWriter writer)
   2: {
   3:     writer.AddAttribute(HtmlTextWriterAttribute.Class, "Menu");
   4:     writer.RenderBeginTag(HtmlTextWriterTag.Div);
   5:     RenderContents(writer);
   6:     writer.RenderEndTag();
   7: }

This is basically creating a div tag, adding the cssclass “Menu” and writes the contents then closes the div tag, this step isn’t required, but if you don’t override it your control will be wrapped in a tag. I have no Idea way the span tag is the default, it just is. The next part is to render the contents, this is done by overriding the RenderContents method with something like this:
   1: protected override void RenderContents(HtmlTextWriter output)
   2: {
   3:     foreach (MenuItemControl item in MenuItems)
   4:     {
   5:         item.RenderControl(output);
   6:     }            
   7: }
Next you just loop though the MenuItmeControls stored in MenuItems and output them into the HtmlTextWriter, and the finished product looks like this:
   1: namespace UserControls
   2: {    
   3:     [DefaultProperty("MenuItems"), ParseChildren(true, "MenuItems")]
   4:     [ToolboxData("<{0}:MenuControl runat=\"server\"></{0}:MenuControl>")]
   5:     public class MenuControl : WebControl
   6:     {        
   7:         [Category("Behavior"),Description("The item collection"), 
   8:             DesignerSerializationVisibility (DesignerSerializationVisibility.Content), 
   9:             PersistenceMode(PersistenceMode.InnerDefaultProperty)]
  10:         public MenuItmeControls MenuItems {get; set;}
  11:  
  12:         protected override void Render(HtmlTextWriter writer)
  13:         {
  14:             writer.AddAttribute(HtmlTextWriterAttribute.Class, "Menu");
  15:             writer.RenderBeginTag(HtmlTextWriterTag.Div);
  16:             RenderContents(writer);
  17:             writer.RenderEndTag();
  18:         }
  19:        
  20:         protected override void RenderContents(HtmlTextWriter output)
  21:         {
  22:             foreach (MenuItemControl item in MenuItems)
  23:             {
  24:                 item.RenderControl(output);
  25:             }            
  26:         }
  27:     }
  28: }
To implement the control, you simply create a web page and add the control like this:
  1: <UC:MenuControl ID="menuControl1" runat="server" >
  2:    <MenuItems>
  3:       <UC:MenuItemControl id="myItem1" runat="server" Selected="true" LinkURL="http://google.com" Text="to Google"></UC:MenuItemControl>
  4:       <UC:MenuItemControl id="myItem2" runat="server" LinkURL="http://bing.com" Text="to Bing"></UC:MenuItemControl>
  5:       <UC:MenuItemControl id="myItem3" runat="server" LinkURL="http://yahoo.com" Text="to Yahoo"></UC:MenuItemControl>                               
  6:    </MenuItems>           
  7: </UC:MenuControl>

That's it, you can add as many menuItemControls as you would like. For the full source for this project go to Source Code Repository

No comments: