Custom List Controls...
Creating custom list controls using the WebControl Class...
Many are familiar with the DropDownList control, which inherits from ListControl. This control contains an Items collection property, which each item is rendered as a child item of the control, as the default property. How is this done? The following article will discuss this in detail. However, ListControl won’t be used; instead, the article uses the WebControl class because it is easier to use.
One of the important factors for how this control works is that only the items collection can appear as child tags within the control. That means all properties must have their PersistenceMode attribute set to Attribute, where the collection has the PersistenceMode set to InnerDefaultProperty. PersistenceMode states how the property is rendered in the HTML, either as an attribute within the control (color=“blue”) or in the inner text (<span>inner text</span>). Any properties having anything other than attribute will cause errors in the parsing of the control. This can be bad for custom styles, which are usually rendered as child elements within the control. However, intellisense is strongly-typed for this implementation, which is a plus. Let’s take a look at this collection property:
|
private EntryCollection _entries; [ DesignerSerializationVisibility( DesignerSerializationVisibility.Content), PersistenceMode(PersistenceMode.InnerDefaultProperty) ] public EntryCollection Entries {   get   {     if (_entries == null)     {       _entries = new EntryCollection();       if (((IStateManager)_entries).IsTrackingViewState)         ((IStateManager)_entries).TrackViewState();     }     return _entries;   } } |
The entries class returns a collection object (instantiates it if null) to the caller, which contains all of the child elements rendered within the tag. One other part is important to making this collection the default property, which occurs in the class definition itself. The ParseChildren attribute has an overload that states a string containing the property name that is the default value, in this case “Entries.”
|
[ ParseChildren(true, "Entries") ] public class NewsControl : WebControl, IStateManager |
The list control has the minimal definition for a collection class; normally, there would be additional functions to help retrieve the entries, but I used just the base class methods inherited from System.Collections.ObjectModel.Collection. This collection class is a generic class, implementing the base methods for collection-based modifications. In addition, this class has code to load and save the viewstate of all its items, but that won’t be discussed here.
So what does the entry look like? The entry class is simple; it defines a title and text property, plus two constructors:
|
#region " Properties " [ Category("Data"), Description("The text for the entry"), PersistenceMode(PersistenceMode.InnerDefaultProperty) ] public string Text {   get { return _text; }   set { _text = value; } } [ Category("Data"), Description("The text for the entry") ] public string Title {   get { return _title; }   set { _title = value; } } #endregion #region " Constructors " public Entry() { } public Entry(string title, string text) {   _title = title;   _text = text; } #endregion |
The attributes I don’t discuss here (they are simple enough and understandable), with the exception of InnerDefaultProperty PersistenceMode. This will render the text within the <{0}:Entry></{0}:Entry> tags; however, one more step is needed to successfully parse the text within those tags.
The Entry class inherits from IParserAccessor, which adds an AddParsedSubObject method. The parser parses inner HTML within the server control and returns the appropriate object type, as determined by the control builder. Inner text will exist as a LiteralControl control, and inner text within a databinding context will exist as a DataBoundLiteralControl control. These controls are allowable within the content of the control; all other instances should throw an error when parsing. In this sense, this method sort of defines the parsing logic.
|
public void AddParsedSubObject(object obj) {   if (obj is LiteralControl)     this.Text = ((LiteralControl)obj).Text;   else if (obj is DataBoundLiteralControl)     this.Text = ((DataBoundLiteralControl)obj).Text;   else     throw new Exception("Error parsing inner text"); } |
It’s actually pretty easy to create a custom list control, with intellisense support. Additional code stores the list in viewstate, but I’ll talk about that in another article. In the code example provided, I grouped article code from several articles into one project, but included them all together. I plan to write articles on a few other topics; however, the code you need to use is in the App_Code folder, and it is the Entry.cs, EntryCollection.cs, and NewsControl.cs classes, as well as the Default.aspx page. The page has all three controls on the page, where the news control is featured top left. The control itself looks like.
|
<m:NewsControl ID="myTest" runat="server">   <m:Entry Title="Test">My Text</m:Entry>   <m:Entry Title="Another Test">My Text</m:Entry> </m:NewsControl> |
A new feature of the 2.0 tools is that references to the namespaces are defined in the web.config, so the web.config has the following XML to reference these controls:
|
<pages>   <controls>     <add tagPrefix="m" namespace="Mains.Examples" assembly="App_Code"/>   </controls> </pages> |
When referencing custom controls in the App_Code (or in a separate project), include the reference in the web.config file.