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 can give developers a better idea of how their current property settings will affect the appearance of your controls at run time. Enabling design-time support for your ASP.NET server controls might be exactly what you need to encourage developers to continue using them.
In this article, you will learn how to use the ControlDesigner class and the GetDesignTimeHtml method to build a design-time presentation for your control. You will learn when you can rely on the default design-time support provided by the .NET Framework and when you need to get in there and do it yourself. Throughout this article, we will demonstrate the capabilities we are describing with a Featured control. This control uses the Random class to render a randomly selected product or other data stored in the DataTable set to the DataSource property. You can use this type of control in e-commerce applications, or you can alter the data source and use it as a Featured articles control. The DataTable can be cached and used to display a different 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't bother to create a custom control designer, it's not the end of the world. Your control will still work; it simply will use the default designer. The default simply 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. What you really want when working with a control in the Visual Studio .NET IDE is a what-you-see-is-what-you-get (WYSIWYG) view of your control and the page it's on. Well, you get what you pay for, and the coding time you spend using the default designer (none) will deliver exactly the appropriate return: nothing.
A reference to the control the designer is designing
Displays the control's class name and instance name
Displays the output from the render method unless overridden
Figure 1. These are the ControlDesigner class's properties and methods used in this example. This is by no means a complete list, but you can download one in this article's sample code.
Choose Your Method
The issue here really is composition vs. rendering. Composition is a straightforward method for building controls; you simply write the code to create instances of existing ASP.NET server controls and any property settings and method calls you require. Because the composition method involves creating child controls, the default designer is unable to render the control at design time. As mentioned earlier, you get what you pay for - because it is relatively easy to build, it doesn't perform quite as well as a control whose rendering logic is custom built.
At this point, it might sound as if rendering is the way to go because the performance will be better than with composition, but there's more to the story. Rendering requires that all rendering logic be built by the control developer. This means if your custom control requires a DataGrid, you must code the rendering logic and handle state management and postback events. Controls that display simple HTML output by way of the render method can, however, take advantage of the default designer because it can display simple output from the render method. So, what's the verdict?
If a server control requires child controls, you must use the CreateChildControls method. If you are using the CreateChildControls method of the WebControl class to build your control's user interface, the default control designer won't do it for you. If you want to display something that looks more like your control will look at run time, you'll have to build a custom control designer.
If the server control requires only simple HTML and no child controls, use the render method. The default designer will take care of the rest.
Figure 2 shows a control using the default designer. The control itself is the starter control Microsoft provides when you start a new Web 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 Text property. Instance 2 has its Text property set to "Text Property."
The default designer can display the value from the control's Text property because the control uses the render method to output the control's user interface to the page. The code that renders the control's user interface is only one line:
Protected Overrides Sub Render
(ByVal output As HtmlTextWriter)
The default designer has no problem displaying the output from the render method because it's simply a TextWriter; there are no special instructions or child controls to create. With a more complex control, such as the Featured control shown in Figure 3 in which child controls are created based on values returned from a database, you need a custom designer to write the code necessary to display the control properly at design time.
Figure 3. Here's the Featured control in the Visual Studio .NET IDE with control properties set.
The Featured control displays products or items of your choice from the DataTable passed to it. In the examples shown here, the data comes from a Products table in a SQL Server database. The products are selected based on the value in the Featured field. If a product is listed as a featured product, it is displayed randomly in the control along with other featured products.
The Featured class has a DataSource property used to select the featured products. The Featured class also has properties for details about the featured item, including itemName, itemImage, itemDescription, and itemPrice.
Although the control itself can be useful in sites required to feature an article, product, or other item, the control is not the star of the show. The Featured control would be difficult to use in developing a production site where the layout of products and other items on the page is essential. Without a custom designer, determining how much space the control's content would require at run time would be impossible, and that would prohibit control and content alignment as well. To solve this problem, a custom designer is used to make the control appear in the IDE at design time exactly how it will appear at run time. In this way, layout is done by aligning the elements of the page visually as they appear on the screen, instead of the more typical method of guessing, aligning, compiling, viewing, and then - oops! - aligning, compiling, and viewing again.
In Figure 4, the GetDesignTimeHtml method is used to create child controls based on property settings. In the GetDesignTimeHtml method, the code from the CreateChildControls method is duplicated. But when the GetDesignTime method is called and the control has been bound to a data source, default property values are used to display the control in the design-time environment for layout purposes. When the control is used to display a single product as defined by the property settings (itemName, ItemPrice, and so on), the property settings are displayed in the design-time environment. When the custom designer is bound, the control appears in the Visual Studio .NET IDE the same way it would appear in the user's browser. This is helpful for layout tasks. Having a control display only the class and instance names in the IDE makes proper layout almost impossible. Imagine you have a control that takes about five lines to display, but, in the IDE, it only appears to take one line to display what I like to call "the green carrot of death," also known as the control handle.
public override string GetDesignTimeHtml()
StringWriter sw = new StringWriter();
HtmlTextWriter tw = new HtmlTextWriter(sw);
Featured ctlFeatured = (Featured) Component;
Table MyTable = new Table();
MyTable.CellPadding = 3;
TableRow rowProductName = new TableRow();
TableRow rowProductDetails = new TableRow();
TableCell celImage = new TableCell();
TableCell celProductImage = new TableCell();
TableCell celProductName = new TableCell();
TableCell celProductDescription = new TableCell();
TableCell celProductPrice = new TableCell();
celProductName.Wrap = false;
celProductName.ColumnSpan = 3;
celProductName.Text = "" +
Image imgProduct = new Image();
imgProduct.ImageUrl = ctlFeatured.itemImageURL;
celProductImage.CssClass = "sectionhead";
celProductImage.Wrap = false;
celProductImage.HorizontalAlign = HorizontalAlign.Center;
celProductDescription.CssClass = "sectionhead";
celProductDescription.Wrap = false;
celProductDescription.Text = ctlFeatured.itemDescription;
celProductPrice.CssClass = "sectionhead";
celProductPrice.Wrap = false;
celProductPrice.HorizontalAlign = HorizontalAlign.Center;
celProductPrice.Text = ctlFeatured.itemPrice;
MyTable.BorderWidth = Unit.Pixel(1);
//add table to control
Figure 4. The GetDesignTimeHtml method from the Featured control is where the magic happens.
The control handle is the standard visual representation of a server control that uses the CreateChildControls method and does not include a custom designer. Although the control handle always is visible in the upper-left corner of all controls, it is the only thing that appears on a custom control with child controls and no designer. As you can see in Figure 5, doing layout with nothing to guide you but the position of the control handle is difficult, if not impossible. Any layout you do based on the control handle that appears in the IDE will be completely invalid when the control handle changes to five lines at design time - things won't line up, to say the least! When you're under pressure to meet a tight deadline on a project, the last thing you need is to have layout issues because the controls you're using aren't WYSIWYG.
Figure 5. Visual Studio .NET displays the "green carrot of death" when a control uses the CreateChildControls method and provides no custom designer.
Once you've created a control designer for your custom server control, there is one final task. After creating your designer, you must bind it to your control. You do this by using an attribute of your control class. As you probably can imagine, the Designer attribute is used to bind the designer to the control class. This is the code to add the Designer attribute to your control class:
public class Featured :
In this example, CustomControls.Designer is the control-designer class named Designer, and CustomControls is the assembly in which the Designer class is defined.
Once the designer is bound to the control class, changes to the control's properties appear dynamically in the Visual Studio .NET IDE. This approach allows page layout to be done by visually placing controls in the proper location as opposed to guessing where they might appear once they contain data.
In summary, when your custom server control requires the use of child controls such as a TextBox or DataGrid, you must build a custom control designer to display the control's output at design time. The control designer must override the ControlDesigner class's GetDesignTimeHtml method. After creating the designer, you must bind it to the control class. Then, you may use your control like any other control in the toolbox. Ah, the toolbox. Now, there arises another issue: Before you can use your control like any other, you must add your control to the toolbox! Follow these steps: Select Customize Toolbox from the Tools menu; select the .NET Framework tab; click on the Browse button; navigate to the folder where your control .dll lives (VB.Net \Bin C# \Bin\Debug); select the .dll and click on Open; and click on OK.
Almost as if by magic, your control appears in the toolbox. Later, in client applications that use your control, you can select your custom control from the toolbox as you would any other control, and a reference to the .dll that contains the control definition will be added to your project automatically.
The sample code in this article is available for download.
Doug Seven is a senior .NET developer for Atomic Consulting Group Inc. with several .NET books to his credit and a few more on the way. Seven is a co-founder of the .NET online resource DotNetJunkies.com, and he has worked with a variety of clients, from start-up companies to Fortune 500 companies, developing everything from desktop- and Web-based applications to bleeding-edge, proof-of-concept applications using mobile devices. E-mail Doug at mailto:[email protected].
Antoine Victor is a senior software developer with more than 10 years of experience writing applications in various programming languages, including VB .NET and C#. Antoine has developed document-management systems, e-commerce Web applications, inventory- and order-tracking systems, and disaster-recovery plans. Antoine currently focuses on high-level consulting projects and spreading the .NET gospel, and he is a co-founder of Professional Data Management. E-mail Victor at mailto:[email protected].
Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.