ptg 1624 CHAPTER 36 Building Custom Controls protected override void RenderContents(HtmlTextWriter writer) { if (_imageItems.Count > 0) { Random rnd = new Random(); ImageItem img = ➥ (ImageItem)_imageItems[rnd.Next(_imageItems.Count)]; writer.AddAttribute(HtmlTextWriterAttribute.Src, img.ImageUrl); writer.AddAttribute(HtmlTextWriterAttribute.Alt, img.AlternateText); writer.RenderBeginTag(HtmlTextWriterTag.Img); writer.RenderEndTag(); } } } public class ImageItem { private string _imageUrl; private string _alternateText; public string ImageUrl { get { return _imageUrl; } set { _imageUrl = value; } } public string AlternateText { get { return _alternateText; } set { _alternateText = value; } } } } The ImageItem class is just a class and does not derive from the base Control class. Because the ImageItem class does nothing more than represent a couple of properties, there is no reason to make it a full-blown control. The page in Listing 36.29 illustrates how you can use the ImageRotator control to display different images randomly. From the Library of Wow! eBook ptg 1625 Working with Control Property Collections 36 LISTING 36.29 ShowImageRotator.aspx <%@ Page Language=”C#” Trace=”true” %> <%@ Register TagPrefix=”custom” Namespace=”myControls” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” ➥ “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <html xmlns=”http://www.w3.org/1999/xhtml” > <head id=”Head1” runat=”server”> <title>Show ImageRotator</title> </head> <body> <form id=”form1” runat=”server”> <div> <custom:ImageRotator id=”ImageRotator1” Runat=”server”> <custom:ImageItem ImageUrl=”Image1.gif” AlternateText=”Image 1” /> <custom:ImageItem ImageUrl=”Image2.gif” AlternateText=”Image 2” /> <custom:ImageItem ImageUrl=”Image3.gif” AlternateText=”Image 3” /> </custom:ImageRotator> </div> </form> </body> </html> The page in Listing 36.29 has tracing enabled. If you look in the Control Tree section, you see that the ImageRotator control does not contain any child controls (see Figure 36.12). FIGURE 36.12 The ShowImageRotator.aspx page control tree. From the Library of Wow! eBook ptg 1626 CHAPTER 36 Building Custom Controls Using the AddParsedSubObject() Method When the ParseChildren attribute has the value false, the contents of a control are auto- matically added to the control’s collection of child controls (represented by the Controls property). You need understand that all content contained in the control, even carriage returns and spaces, are added to the controls collection. Any content contained in a control that does not represent a server-side control is parsed into a Literal control. In some cases, you might want to allow only a certain type of control to be added to the Controls collection. The AddParsedSubObject() method is called as each control is added to the Controls collection. By overriding the AddParsedSubObject() method, you can block certain types of controls—such as Literal controls—from being added to the Controls collection. For example, the ContentRotator control in Listing 36.20 overrides the base AddParsedSubObject() method and prevents anything that is not a Content control from being added to the ContentRotator Controls collection. If you removed the AddParsedSubObject() method from this control, all the carriage returns and spaces between the Content controls would be added to the Controls collection as Literal controls. Using a ControlBuilder The AddParsedSubObject() method enables you to specify which parsed controls get added to a Controls collection. Sometimes, you must take even more control over the parsing of a control. When the ASP.NET Framework parses a page, the Framework uses a special type of class called a ControlBuilder class. You can modify the way in which the content of a control is parsed by associating a custom ControlBuilder with a control. Here’s a list of the most useful methods supported by the ControlBuilder class: . AllowWhiteSpaceLiterals()—Enables you to trim white space from the contents of a control. . AppendLiteralString()—Enables you trim all literal content from the contents of a control. . GetChildControlType()—Enables you to specify how a particular tag gets parsed into a control. The GetChildControlType() method is the most useful method and enables you to map tags to controls. You can use the GetChildControlType() method to map any tag to any control. For example, the file in Listing 36.30 contains a ServerTabs control that renders multiple tabs (see Figure 36.13). Each tab is represented by a Tab control. From the Library of Wow! eBook ptg 1627 Working with Control Property Collections 36 LISTING 36.30 ServerTabs.cs using System; using System.Collections; using System.Web.UI; using System.Web.UI.WebControls; namespace myControls { [ControlBuilder(typeof(ServerTabsBuilder))] [ParseChildren(false)] public class ServerTabs : WebControl, IPostBackEventHandler { public int SelectedTabIndex { get { if (ViewState[“SelectedTabIndex”] == null) return 0; else return (int)ViewState[“SelectedTabIndex”]; } set FIGURE 36.13 Using the ServerTabs control. From the Library of Wow! eBook ptg 1628 CHAPTER 36 Building Custom Controls { ViewState[“SelectedTabIndex”] = value; } } protected override void RenderContents(HtmlTextWriter writer) { for (int i = 0; i < this.Controls.Count; i++) { ServerTab tab = (ServerTab)this.Controls[i]; string eRef = Page.ClientScript.GetPostBackClientHyperlink(this, i.ToString()); if (SelectedTabIndex == i) writer.AddAttribute( HtmlTextWriterAttribute.Class, “tab selectedTab”); else writer.AddAttribute(HtmlTextWriterAttribute.Class, “tab”); writer.RenderBeginTag(HtmlTextWriterTag.Div); writer.AddAttribute(HtmlTextWriterAttribute.Href, eRef); writer.RenderBeginTag(HtmlTextWriterTag.A); writer.Write(tab.Text); writer.RenderEndTag(); // A writer.RenderEndTag(); // Tab DIV } writer.Write(“<br style=’clear:both’ />”); writer.AddAttribute(HtmlTextWriterAttribute.Class, “tabContents”); writer.RenderBeginTag(HtmlTextWriterTag.Div); this.Controls[SelectedTabIndex].RenderControl(writer); writer.RenderEndTag(); // Tab Contents DIV } protected override void AddParsedSubObject(object obj) { if (obj is ServerTab) base.AddParsedSubObject(obj); } protected override HtmlTextWriterTag TagKey { get { return HtmlTextWriterTag.Div; } } From the Library of Wow! eBook ptg 1629 Working with Control Property Collections 36 public void RaisePostBackEvent(string eventArgument) { SelectedTabIndex = Int32.Parse(eventArgument); } } public class ServerTabsBuilder : ControlBuilder { public override Type GetChildControlType(string tagName, IDictionary attribs) { if (String.Compare(tagName, “tab”, true) == 0) return typeof(ServerTab); else return null; } } public class ServerTab : Control { private string _Text; public string Text { get { return _Text; } set { _Text = value; } } } } The ServerTabs class is decorated with a ControlBuilder attribute. This attribute associ- ates the ServerTabs control with a ControlBuilder class named ServerTabsBuilder. The ServerTabsBuilder class overrides the base ControlBuilder GetChildControlType() method. The overridden method maps the <tab> tag to the Tab control. Because of this mapping, you do not need to use a prefix or use the runat=”server” attribute when declaring a tab within the ServerTabs control. The page in Listing 36.31 illustrates how you can use the ServerTabs control. LISTING 36.31 ShowServerTabs.aspx <%@ Page Language=”C#” %> <%@ Register TagPrefix=”custom” Namespace=”myControls” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” ➥ “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> From the Library of Wow! eBook ptg 1630 CHAPTER 36 Building Custom Controls <html xmlns=”http://www.w3.org/1999/xhtml” > <head id=”Head1” runat=”server”> <style type=”text/css”> html { background-color:silver; } .tab { float:left; position:relative; top:1px; background-color:#eeeeee; border:solid 1px black; padding:0px 15px; margin-left:5px; } .tab a { text-decoration:none; } .selectedTab { background-color:white; border-bottom:solid 1px white; } .tabContents { border:solid 1px black; background-color:white; padding:10px; height:200px; } </style> <title>Show ServerTabs</title> </head> <body> <form id=”form1” runat=”server”> <div> <custom:ServerTabs ID=”ServerTabs1” Runat=”Server”> <tab Text=”First Tab”> Contents of the first tab </tab> From the Library of Wow! eBook ptg 1631 Creating a Better Designer Experience 36 <tab Text=”Second Tab”> Contents of the second tab </tab> <tab Text=”Third Tab”> Contents of the third tab </tab> </custom:ServerTabs> </div> </form> </body> </html> The ControlBuilder enables you to declare instances of the Tab control by using the <tab> tag instead of using a <custom:Tab runat=”server”> tab. Creating a Better Designer Experience Up to this point, we’ve ignored the Design view experience. In other words, we’ve ignored the question of how our custom controls appear in the Visual Web Developer or Visual Studio .NET Design view. You can modify the appearance of your control in Design view in two ways. You can apply design-time attributes to the control, or you can associate a ControlDesigner with your control. We explore both methods in this section. Applying Design-Time Attributes to a Control Design-time attributes enable you to modify how control properties appear in Design view. Some attributes are applied to the control itself, whereas other attributes are applied to particular properties of a control. Here is the list of the design-time attributes you can apply to a control: . DefaultEvent—Enables you to specify the default event for a control. When you double-click a control in Visual Web Developer or Visual Studio .NET, an event handler is automatically created for the default event. . DefaultProperty—Enables you to specify the default property for a control. When you open the Property window for a control, this property is highlighted by default. . PersistChildren—Enables you to specify whether child controls or properties are persisted as control attributes or control contents. . ToolboxData—Enables you to specify the tag added to a page when a control is dragged from the toolbox. From the Library of Wow! eBook ptg 1632 CHAPTER 36 Building Custom Controls . ToolboxItem—Enables you to block a control from appearing in the Toolbox. Here is the list of design-time attributes you can apply to a control property: . Bindable—Enables you to indicate to display a Databindings dialog box for the property. . Browsable—Enables you to block a property from appearing in the Properties window. . Category—Enables you to specify the category associated with the property. The property appears under this category in the Properties window. . DefaultValue—Enables you to specify a default value for the property. When you right-click a property in the Properties window, you can select Reset to the return the property to its default value. . Description—Enables you to specify the description associated with the property. The description appears in the Properties window when the property is selected. . DesignerSerializationVisibility—Enables you to specify how changes to a prop- erty are serialized. Possible values are Visible, Hidden, and Content. . Editor—Enables you to specify a custom editor for editing the property in Design view. . EditorBrowsable—Enables you to block a property from appearing in Intellisense. . NotifyParentProperty—Enables you to specify that changes to a subproperty should be propagated to the parent property. . PersistenceMode—Enables you to specify whether a property is persisted as a control attribute or control content. Possible values are Attribute, EncodedInnerDefaultProperty, InnerDefaultProperty, and InnerProperty. . TypeConverter—Enables you to associate a custom type converter with a property. A type converter converts a property between a string representation and a type (or vice versa). The Editor attribute enables you to associate a particular editor with a property. Certain types in the Framework have default editors. For example, a property that represents a System.Drawing.Color value is automatically associated with the ColorEditor. The ColorEditor displays a color picker (see Figure 36.14). To view the list of editors included in .NET Framework, look up the UITypeEditor class in the .NET Framework SDK Documentation. The MovieView control contained in Listing 36.32 illustrates how you can use several of these attributes. The control displays a single movie. From the Library of Wow! eBook ptg 1633 Creating a Better Designer Experience 36 LISTING 36.32 MovieView.cs using System; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; namespace myControls { [DefaultProperty(“Title”)] public class MovieView : WebControl { private string _title = “Movie Title”; private string _description = “Movie Description”; [Category(“Movie”)] [Description(“Movie Title”)] public string Title { FIGURE 36.14 Using the ColorEditor to pick a color. From the Library of Wow! eBook . EncodedInnerDefaultProperty, InnerDefaultProperty, and InnerProperty. . TypeConverter—Enables you to associate a custom type converter with a property. A type converter converts a property between a string representation. changes to a subproperty should be propagated to the parent property. . PersistenceMode—Enables you to specify whether a property is persisted as a control attribute or control content. Possible values. property. The description appears in the Properties window when the property is selected. . DesignerSerializationVisibility—Enables you to specify how changes to a prop- erty are serialized. Possible