When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article
Jobs

ASP ASP.NET ASP FAQs Message Board Feedback ASP Jobs
 
Print this Page!
Published: Wednesday, August 11, 2004

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:

  1. 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.
  2. 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.

- continued -

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:
  1. Set the PersistChildren and ParseChildren attributes of the RoundedCorners Web control to the appropriate values.
  2. Turn RoundedCorners into a rendered Web control from a composite control.
  3. 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:

  1. 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>
    

  2. 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():

string filePath = Page.ResolveUrl(string.Concat(ImageDirectory, "1x1.gif"));

And Second in CreateCorner():

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.

Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the complete code (in ZIP format)
  • View a live demo


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article