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.
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
andParseChildren
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
.
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" ...>
|
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.
<asp:DataGrid runat="server" AutoGenerateColumns="False" ...> <ItemStyle BackColor="Peach" Font-Name="Verdana" ...></ItemStyle> <Columns> <asp:BoundColumn ... /> ... </Columns> </asp:DataGrid>
- 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>
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:
|
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:
|
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

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
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 |
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.
Happy Programming!
Attachments: