Build a Control Designer
Improve your users’ design-time experience.
October 30, 2009
asp:feature
LANGUAGES: C#
TECHNOLOGIES: Custom Controls
Build a Control Designer
Improve your users' design-time experience.
By Antoine Victor and Doug Seven
With a well-implemented design-time experience, you cangive developers a better idea of how their current property settings willaffect the appearance of your controls at run time. Enabling design-timesupport for your ASP.NET server controls might be exactly what you need toencourage developers to continue using them.
In this article, you will learn how to use theControlDesigner class and the GetDesignTimeHtml method to build a design-timepresentation for your control. You will learn when you can rely on the defaultdesign-time support provided by the .NET Framework and when you need to get in thereand do it yourself. Throughout this article, we will demonstrate thecapabilities we are describing with a Featured control. This control uses theRandom class to render a randomly selected product or other data stored in theDataTable set to the DataSource property. You can use this type of control ine-commerce applications, or you can alter the data source and use it as aFeatured articles control. The DataTable can be cached and used to display adifferent featured product on each refresh of the page.
Use the ControlDesigner Class
The ControlDesigner class implements a few useful methods,some of which are listed in Figure 1. If you build a new control and don'tbother to create a custom control designer, it's not the end of the world. Yourcontrol will still work; it simply will use the default designer. The defaultsimply displays the name of the class and the name of the instance of the class(the ID property of the control). In most cases, that's not what you want. Whatyou really want when working with a control in the Visual Studio .NET IDE is awhat-you-see-is-what-you-get (WYSIWYG) view of your control and the page it'son. Well, you get what you pay for, and the coding time you spend using thedefault designer (none) will deliver exactly the appropriate return: nothing.
Property Name | Description |
---|---|
Component | A reference to the control the designer is designing |
Method Name | Description |
---|---|
GetEmptyDesignTimeHtml | Displays the control's class name and instance name |
GetDesignTimeHtml | Displays the output from the render method unless overridden |
Figure 1. These are the ControlDesigner class'sproperties and methods used in this example. This is by no means a completelist, but you can download one in this article's sample code.
Choose Your Method
The issue here really is composition vs. rendering. Compositionis a straightforward method for building controls; you simply write thecode to create instances of existing ASP.NET server controls and any propertysettings and method calls you require. Because the composition method involvescreating child controls, the default designer is unable to render the controlat design time. As mentioned earlier, you get what you pay for - because it isrelatively easy to build, it doesn't perform quite as well as a control whoserendering logic is custom built.
At this point, it might sound as if rendering is the wayto go because the performance will be better than with composition, but there'smore to the story. Rendering requires that all rendering logic be builtby the control developer. This means if your custom control requires aDataGrid, you must code the rendering logic and handle state management andpostback events. Controls that display simple HTML output by way of the rendermethod can, however, take advantage of the default designer because it candisplay simple output from the render method. So, what's the verdict?
If a server control requires child controls, you must usethe CreateChildControls method. If you are using the CreateChildControls methodof the WebControl class to build your control's user interface, the defaultcontrol designer won't do it for you. If you want to display something thatlooks more like your control will look at run time, you'll have to build acustom control designer.
If the server control requires only simple HTML and nochild controls, use the render method. The default designer will take care ofthe rest.
Figure 2 shows a control using the default designer. Thecontrol itself is the starter control Microsoft provides when you start a newWeb control project.
Figure 2. This is how the starter control appears in the Visual Studio.NET IDE when using the default designer. Instance 1 has an empty Textproperty. Instance 2 has its Text property set to "Text Property."
The default designer can display the value from thecontrol's Text property because the control uses the render method to outputthe control's user interface to the page. The code that renders the control'suser interface is only one line:
Protected Overrides Sub Render
(ByVal output AsHtmlTextWriter)
output.Write([Text])
End Sub
The default designer has no problem displaying the outputfrom the render method because it's simply a TextWriter; there are no specialinstructions or child controls to create. With a more complex control, such asthe Featured control shown in Figure 3 in which child controls are createdbased on values returned from a database, you need a custom designer to writethe code necessary to display the control properly at design time.
Figure 3. Here's the Featured control in the Visual Studio .NET IDE withcontrol properties set.
The Featured control displays products or items of yourchoice from the DataTable passed to it. In the examples shown here, the datacomes from a Products table in a SQL Server database. The products are selectedbased on the value in the Featured field. If a product is listed as a featuredproduct, it is displayed randomly in the control along with other featuredproducts.
The Featured class has a DataSource property used toselect the featured products. The Featured class also has properties fordetails about the featured item, including itemName, itemImage,itemDescription, and itemPrice.
Although the control itself can be useful in sites requiredto feature an article, product, or other item, the control is not the star ofthe show. The Featured control would be difficult to use in developing aproduction site where the layout of products and other items on the page isessential. Without a custom designer, determining how much space the control'scontent would require at run time would be impossible, and that would prohibitcontrol and content alignment as well. To solve this problem, a custom designeris used to make the control appear in the IDE at design time exactly how itwill appear at run time. In this way, layout is done by aligning the elementsof the page visually as they appear on the screen, instead of the more typicalmethod of guessing, aligning, compiling, viewing, and then - oops! - aligning,compiling, and viewing again.
In Figure 4, the GetDesignTimeHtml method is used tocreate child controls based on property settings. In the GetDesignTimeHtmlmethod, the code from the CreateChildControls method is duplicated. But whenthe GetDesignTime method is called and the control has been bound to a datasource, default property values are used to display the control in thedesign-time environment for layout purposes. When the control is used todisplay a single product as defined by the property settings (itemName,ItemPrice, and so on), the property settings are displayed in the design-timeenvironment. When the custom designer is bound, the control appears in theVisual Studio .NET IDE the same way it would appear in the user's browser. Thisis helpful for layout tasks. Having a control display only the class andinstance names in the IDE makes proper layout almost impossible. Imagine youhave a control that takes about five lines to display, but, in the IDE, it onlyappears to take one line to display what I like to call "the green carrot ofdeath," also known as the control handle.
public override string GetDesignTimeHtml()
{
StringWriter sw = newStringWriter();
HtmlTextWriter tw = newHtmlTextWriter(sw);
Featured ctlFeatured = (Featured) Component;
Table MyTable = newTable();
MyTable.CellPadding = 3;
TableRowrowProductName = new TableRow();
TableRowrowProductDetails = new TableRow();
TableCell celImage = new TableCell();
TableCellcelProductImage = new TableCell();
TableCellcelProductName = new TableCell();
TableCellcelProductDescription = newTableCell();
TableCellcelProductPrice = new TableCell();
celProductName.Wrap =false;
celProductName.ColumnSpan = 3;
celProductName.Text ="" +
ctlFeatured.itemName +
"";
rowProductName.Cells.Add(celProductName);
MyTable.Rows.AddAt(0,rowProductName);
Image imgProduct = new Image();
imgProduct.ImageUrl =ctlFeatured.itemImageURL;
celProductImage.CssClass= "sectionhead";
celProductImage.Wrap =false;
celProductImage.HorizontalAlign = HorizontalAlign.Center;
celProductImage.Controls.Add(imgProduct);
rowProductDetails.Cells.Add(celProductImage);
celProductDescription.CssClass = "sectionhead";
celProductDescription.Wrap = false;
celProductDescription.HorizontalAlign =
HorizontalAlign.Center;
celProductDescription.Text = ctlFeatured.itemDescription;
rowProductDetails.Cells.Add(celProductDescription);
celProductPrice.CssClass= "sectionhead";
celProductPrice.Wrap =false;
celProductPrice.HorizontalAlign = HorizontalAlign.Center;
celProductPrice.Text =ctlFeatured.itemPrice;
rowProductDetails.Cells.Add(celProductPrice);
MyTable.Rows.Add(rowProductDetails);
MyTable.BorderWidth =Unit.Pixel(1);
//add table to control