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 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.
|
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'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)
output.Write([Text])
End Sub
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 =
"<B>" +
ctlFeatured.itemName +
"</B>";
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
MyTable.RenderControl(tw);
return sw.ToString();
}
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:
[Designer("CustomControls.Designer, CustomControls"),
DefaultProperty("itemName"),
ToolboxData("<{0}:Featured
runat=server></{0}:Featured>")]
public class Featured :
System.Web.UI.WebControls.WebControl
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:dseven@dotnetjunkies.com.
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:Antoine@prodataman.com.
Tell us what you think! Please send
any comments about this article to mailto:feedback@aspnetPRO.com.
Please include the article title and author.