Improving the RoundedCorners Web Control
By Scott Mitchell
Introduction
A couple weeks ago I created a custom ASP.NET server control for displaying some text within a box with rounded corners.
This control, which I called RoundedCorner, was discussed in detail in the article Introducing the
RoundedCorners Web Control. Ideally, such rounded corner boxes are created by having a graphics designer create the
necessary rounded corner images using Photoshop or some other graphics editing program. The HTML designer, then, can create
the necessary markup (HTML and/or CSS) to display a box using these rounded corner images. While this is indeed the "best practices"
approach for creating a box with rounded corners, it is not always feasible for those of us who are developer teams of one, and
whose artistic skills match those of a brain-damaged poodle. The RoundedCorners Web control aids those of us, like me, who
fall in this boat by dynamically creating the corner images using GDI+.
Since the publication of the original RoundedCorners article, I have received a lot of great feedback on suggestions for
improvements. The main two suggestions were:
Add the ability to put anything inside a box with rounded corners, not just some text message. That is, I
should be able to have a DataGrid, some HTML markup, and a TextBox Web control all within a RoundedCorners Web control
instance.
Use anti-aliasing when generating the images, to help smooth out the rounded corners.
I have, in the past two weeks, implemented these two enhancements. In this article I will discuss how these enhancements were
made, and demonstrate using them. At the end of this article you can download the updated RoundedCorners Web control (including
the complete source code), as well as a link to some live demos.
Opening Up the RoundedCorners Control
The previous incarnation of RoundedCorners had a string Text property. The value of this property was the text
(or HTML markup) displayed within the box with rounded corners. Clearly a page developer would have a lot more freedom in
displaying content within a box with rounded corners if, rather than having to specify a string property, she could simply
drag and drop controls into the box, much like page developers can add Web controls to the Panel Web control. To allow for
this, I did the following three things:
Set the PersistChildren and ParseChildren attributes of the RoundedCorners Web control to
the appropriate values.
Turn RoundedCorners into a rendered Web control from a composite control.
Created a design-time class that derived from System.Web.UI.Design.ReadWriteControlDesigner.
Let's look at each of these three steps individually.
Using the PersistChildren and ParseChildren Attributes
All ASP.NET server controls can utilize a number of attributes to
specify metadata about the control. Commonly this metadata is used by Visual Studio .NET to enhance the design-time experience,
but some attributes are used for other purposes. Two important attributes are PersistChildren and
ParseChildren, which dictate how a Web control reacts to any child content within its tags. Child content
is the markup that appears between the starting and closing tags for a control's declarative syntax:
<asp:SomeWebControl runat="server" ...> This content in here is considered the child content...
</asp:SomeWebControl>
The child content can be interpreted in two ways:
As property values - many built-in ASP.NET Web controls specify property values as child content. For example,
the DataGrid class will specify style information, as well as column information as child content. This actual content
maps to properties in the DataGrid class.
As children controls - the child content can be interpreted as controls that should be added to the
Web control's control hierarchy. The Panel Web control, for example, has the HTML and Web controls that appear
within it specified in its child content area:
<asp:Panel runat="server" ...>
What is your name?<br />
<asp:TextBox runat="server" ... />
</asp:Panel>
To indicate which of these two models should be used, the PersistChildren and
ParseChildren attributes are used. By default, the PersistChildren and
ParseChildren have values of False and True. The PersistChildren
attribute specifies whether Visual Studio .NET should persist the child content as children controls.
The ParseChildren attribute indicates whether or not the child content should be parsed as properties.
So, when PersistChildren is False and ParseChildren is True, the behavior is that
inner XML content is treated as property values (again, this is the default behavior). If you want to child content
to be treated as child controls in the control hierarchy, you need to explicitly set PersistChildren to True
and ParseChildren is False, like so:
[
PersistChildren(true),
ParseChildren(false),
...
]
public class RoundedCorners : System.Web.UI.WebControls.WebControl
{
...
}
For more information on these two attributes, and how they affect how the child content in the declarative syntax of a
Web control is interpreted, be sure to read: Using
ParseChildrenAttribute.
Converting from a Composite Control to a Rendered Control
Previously RoundedCorners was a composite control, meaning that I overrided the CreateChildControls() method
and programmatically constructed a child control hierarchy for the control. This control hierarchy contained the necessary
controls to display a box with rounded corners, with the specified Text inside. While composite controls have their
place, they aren't suited for a control where the page developer can specify the control hierarchy through the declarative syntax.
In such a case, it's better to go with a rendered control.
A rendered control is one that emits the correct markup explicitly for the control during the Render stage. The markup is
emitted by calls to the HtmlTextWriter instance passed into the control's RenderControl() method.
Rendered controls and composite controls are two different ways to generate the appropriate markup for a custom server control.
A thorough discussion of these two techniques is beyond the scope of this article, but you can learn more at:
Composition vs.
Rendering. The key thing to understand is that for a control that can have its control hierarchy specified declaratively
by the page developer, a rendered control likely makes more sense. Hence, I refactored RoundedCorners to use the rendering technique
as opposed to the composition technique.
Creating a Design-Time Class
With the two changes above, the RoundedCorners control allows for arbitrary markup in the child content region of the
control's declarative syntax. That is, you could create the RoundedControl with a DataGrid in it using declarative syntax
like:
<skm:RoundedCorners runat="server" ...>
Here is the data:
<br />
<asp:DataGrid runat="server" ...>
...
</asp:DataGrid>
</skm:RoundedCorners runat="server">
However, the design-time experience is woefully lacking. What we'd like to be able to do is have the design-time experience
mimic that of the Panel Web control, enabling page developers to simply drag and drop controls from the Toolbox into the
RoundedCorners control. To accomplish this we need to create a custom designer class that derives from System.Web.UI.Design.ReadWriteControlDesigner.
A ReadWriteControlDesigner provides a simple HTML interface in the designer into which a page developer and
drag-and-drop controls. Sadly we, the control designer, have very little control over the actual HTML used in the VS.NET designer.
While the ReadWriteControlDesigner has a GetDesignTimeHtml() method, it's never called. All we
can do is specify the style properties for the design-time interface.
The good news is that the design-time experience of RoundedCorners allows for drag-and-drop, and a somewhat WYSIWYG editing
interface. The bad news is that that editing interface is anything but ideal. Since the custom designer class cannot
richly specify the HTML that should be rendered in Visual Studio .NET's Design view, the RoundedCorners can show only the
"body" of the box with rounded corners. That is, in the Designer you won't see any rounded corners, or even the title of
the box (if specified). But these will appear when viewed through a Web browser. The screenshot to the right shows
a view of the RoundedCorners control in the VS.NET Designer; note that the rounded corners are nowhere to be found, and the
title is not shown.
Using Anti-Aliased Rounded Corners
Another suggestion I received was to anti-alias the rounded corner images. Anti-aliasing
is the process by which the rough edges of a curve are smoothed. Anti-aliasing works by taking the colors at the edge of
a line and smoothing them with an intermediate color that's inbetween the color of the line and the background. For
example, if you had a black, curved line, ant-aliasing would add some grey pixels along the edge of the line so that, from
a human's perspective, the line looked smooth and not jagged. An example of two lines - the top one anti-aliased, the bottom
one not - can be seen on the left.
Fortunately anti-aliasing with GDI+ is fairly simple. The Graphics class has a SmoothingMode property
that can be assigned one of the following values from the System.Drawing.Drawing2D.SmoothingMode enumeration:
Default (the default)
AntiAlias
HighQuality
HighSpeed
Invalid
None
There is one catch with anti-aliasing, though: anti-aliasing cannot be done between a line whose edge is with the transparent
color. Anti-aliasing smooths a line by adding an intermediate color between the two colors at an edge, so if one of those
colors is the transparent color, well, then, the algorithm has no way of knowing what color will be there, and therefore
can't perform anti-aliasing.
(Thanks to Chris Garrett who helped explain this concept to me...)
To overcome this, I added an optional property to the RoundedCorners Web control called BackgroundBackColor (a bit of
a tongue-twister). If this property is omitted, then the rounded corner image has its exterior painted with the transparent color,
so that it doesn't matter the color of the background you add the RoundedCorners control to. If, however, you explicitly provide
a BackgroundBackColor, the exterior of the rounded corner will be filled with the specified color and the
image will be anti-aliased. The downside of providing a BackgroundBackColor is that you must know the color behind
the rounded corner box, and if that color changes, users will see rounded corners with a background that doesn't match the color behind it.
(Special thanks to Nick Gilbert for helping with some anti-aliasing issues and
doing a great job in explaining the concepts to me...)
Using the ~ in the ImageDirectory Property
Avid 4Guys reader Mel G. writes in to share:
I've made a small improvement that I'd like to share with you. By adding a call to
Page.ResolveUrl() the control can now resolve the tilde (~) in ImageDirectory
references. As far as I can see, the change only needs to be made in two places.
First, in Create1x1Gif():
StringBuilder fileName = new StringBuilder(Page.ResolveUrl(ImageDirectory), 75);
Now I don't have to worry about relative pathing issues.
Using the RoundedCorners Web Control in an ASP.NET Web Page
At the end of this article you will find the complete source code as well as an assembly (a .dll file) compiled for the .NET Framework
1.1. If you are using Visual Studio .NET 2003, you can simply add the RoundedCorners control to your Toolbox by
right-clicking on the VS.NET Toolbox, choosing Add/Remove Items, and browsing to the RoundedCorners assembly. (If you
are using Visual Studio .NET 2002, you'll need to compile the provided source code and use that compiled assembly.)
Once you have added the control to the Toolbox, adding it to an ASP.NET Web page is as simple as dragging the control
from the Toolbox and dropping it onto the page's Designer.
Conclusion
Thanks for many great suggestions, the RoundedCorners control has been improved in two ways: by allowing page developers to
add any content into the box with rounded corners, and allowing anti-aliasing of the auto-generated GIF images.