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, July 28, 2004

Introducing the RoundedCorner Web Control

By Scott Mitchell


More Information About this Control!
A new article has been written with more information about this Web control. Once you have read this article, be sure to read Improving the RoundedCorners Web Control.

Introduction


Many professionally done sites group related options into aesthetically-pleasing "boxes". On Amazon.com, for example, the left-hand column contains a number of these boxes: Search, Browse, and so on. Each box has a bordered outline, with a title at the top. To gussy things up a bit, the title bar at the top of the box has rounded corners, as opposed to just square corners.

This is an example of text inside of a RoundedCorners control that has a width of 150 pixels. Neat, eh?
Creating a box with rounded corners isn't terribly difficult, and there's a myriad of ways for creating such design elements. Virtually all of the techniques, though, require using Photoshop or some other graphics editing program to create rounded corners of the appropriate color. For graphics designers, creating rounded corners must be extremely boring, and they likely hold as much interest in creating rounded corners as veteran ASP.NET developers do in creating a connection to a database. Been there, done that - thousands of times. But for non-graphics designers (like yours truly), creating these rounded corners can be frustratingly slow and annoying, especially if you're like me, and the only graphics editing program you have is Microsoft Paint.

To overcome this loathsome activity, I decided to create a custom ASP.NET server control that would utilize the dynamic image-creation capabilities of GDI+ to create the corner images for me. The result is an ASP.NET Web control, which I call RoundedCorners, that you can drop on an ASP.NET Web page, set a few properties, and be presented with a nifty box with rounded corners, as shown on the right. In this article we'll examine how to use the RoundedCorners Web control in a page, as well as take a peek under the covers at the control's source code.

- continued -

Techniques for Creating Boxes with Rounded Corners


Before we delve into how to use the RoundedCorners control, let's first discuss some of the techniques that can be employed to create a box with rounded corners:

  1. Create the box, rounded corners, and text, all as a single image.
  2. Use a three-column, three-row HTML <table>. With this approach, the left-most and right-most columns in the top and bottom rows each contain one of the four corners. A variant of this approach is to create a one-column, three-row table, where the top and bottom rows are complete images themselves. (That is, rather than having to create just the corner, you create the entire bottom or top row as an image.)
  3. Use CSS-related techniques. Typically a few nested <div> tags are used with some relative positioning for the corners. For more information on using CSS simply Google CSS rounded corners, and you'll find a wealth of information.

With all of these approaches, however, one or more images are needed. (CSS3 offers the ability to create rounded corners and drop shadows without the use of images; unfortunately, CSS3 is still in the formation stages, and while some of its features are supported by some browsers, it's not anywhere close to being uniformly supported.)

The RoundedCorners Web control uses a 3x3 HTML <table> to create the rounded corners. For those CSS purists out there... well... sorry. The source code for this control is available at the end of this article, so you are more than welcome to modify the control to emit CSS syntax. (If you do enhance the control, though, please send me the updated version, as I can share it here with others.)

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.

Not an Ideal Design-Time Experience
One of the nice things about Visual Studio .NET is the design-time experience for ASP.NET page developers. When you add a control to the page's Designer, the Designer renders the control as you would see it in the browser. Change the control's properties, and its appearance in the Designer changes automatically, too. RoundedCorners, however, does not display the rounded corners in the Designer - instead there are broken image links.

Once you add the RoundedCorners control to an ASP.NET Web page, you'll be able to configure it's properties via the Properties pane. The germane properties for this control are:

  • BackColor - specifies the background color for the main area in the box.
  • BorderColor / BorderStyle / BorderWidth - specifies the border color, style, and width for the box.
  • CornerHeight / CornerWidth - specifies the height and width for the corner images. These default to 13 pixels, but you can tweak the size if needed. (If you have a large title row, you might need to make the corners higher than 13 pixels.)
  • Font - specifies the font style information for the box's text. (Font name, size, bold, italic, etc.)
  • ForeColor - specifies the foreground color of the box's text.
  • HorizontalAlign - indicates how the box is horizontally aligned.
  • ImageDirectory - the RoundedCorners control only creates the corner images on a per-demand basis. After a corner image is created, it is saved to the Web server's file system, so that the image need not be recreated for each page load from each visitor. The ImageDirectory property specifies the virtual directory where the images should go (i.e., /Images/).
  • Padding - the padding around the box's text.
  • RoundedBottom - determines whether or not rounded corners are also displayed at the bottom of the box. Defaults to True, meaning that rounded corners are placed on both the bottom and top.
  • TextHorizontalAlign - indicates how the text within the box is horizontally aligned.
  • Title - specifies the title for the box. If a title is specified, it is placed in the middle column of the <table>'s top row.
  • TitleStyle - provides style information for the title row, such as background color, font information, and so on.
  • Width - indicates the width of the box. This is optional, but oftentimes you'll want to give an absolute width for the box, such as 150px.

You can configure all of the properties for RoundedCorners through the Visual Studio .NET Properties pane and Designer, or you can declaratively specify the property values. I have a series of live demos available where you can see the resulting rounded corners box and the declarative markup that goes with it.

Looking Under the Hood at the RoundedCorners Control's Source Code


The code for the RoundedCorners server control is fairly straightforward. RoundedCorners is implemented as a composite control derived from the WebControl class. In the overridden CreateChildControls() method a Table is created with three TableRows, each with three TableCells. The upper-left, upper-right, bottom-left, and bottom-right cells display an appropriately rounded corner. The upper-middle cell displays the title, if specified.

The rounded corners are displayed by adding an Image Web control to the appropriate cells. This Image Web control is returned by a call to the method CreateCorner(). If one were to naively implement CreateCorner(), they'd have it use GDI+ to dynamically generate a rounded corner image each time is was called, but this is quite wasteful. Imagine a Web page has a box with rounded corners. When it is visited for the first time, these four corner images will be dynamically created. Now, if another user visits this same page, he'll need the same images; we could recreate them on the fly, but why not save the images from the first time they were created? The purpose of CreateCorner(), then, is to:

  1. Check to see if an image file exists on the file system for the rounded corner.
  2. If the file does not exist, dynamically create it and save it.
  3. Return an Image Web control whose ImageUrl property equals the appropriate filename.

This is a pretty sensible approach, but there is one wrinkle. The rounded corner images are specific to a number of parameters, including:

  • Which of the four corners is being created,
  • The background color of the box,
  • The box's border color, style, and width, and
  • The specified height and width of the corner images.

For example, imagine that you have a box with a light blue background and a solid navy border with 1px width. When the page containing this rounded corner box is visited, the four corner images will be generated dynamically, and then saved to the file system. When subsequent visitors come, they'll be served the corner images that were saved to disk. Great. But now imagine that our page developer changes the background color to white. If he doesn't delete the associated corner images, visitors will now see a box with a white background, but whose corners have a light blue background!

To overcome this problem, we can give each corner a filename that spells out its defining characteristics. This is precisely what CreateCorner() does, as can be seen by the following code snippet. (Note that CreateCorner() takes in a parameter of type Corners - this is an enumeration with the four possible corner positions: UpperLeft, UpperRight, BottomLeft, and BottomRight.)

protected virtual System.Web.UI.WebControls.Image CreateCorner(Corners c)
{
   // compute the file name
   StringBuilder fileName = new StringBuilder(ImageDirectory, 75);

   // start with the corner abbreviation
   switch (c)
   {
      case Corners.UpperLeft:
         fileName.Append("ul");

         // now add the backcolor
         if (TitleStyle.BackColor != Color.Empty)
            fileName.AppendFormat(".{0:x2}{1:x2}{2:x2}", TitleStyle.BackColor.R, 
                                  TitleStyle.BackColor.G, TitleStyle.BackColor.B);
         else
            fileName.AppendFormat(".{0:x2}{1:x2}{2:x2}", BackColor.R, 
                                                      BackColor.G, BackColor.B);
         break;
      case Corners.UpperRight:
         fileName.Append("ur");

         // now add the backcolor
         if (TitleStyle.BackColor != Color.Empty)
            fileName.AppendFormat(".{0:x2}{1:x2}{2:x2}", TitleStyle.BackColor.R, 
                                   TitleStyle.BackColor.G, TitleStyle.BackColor.B);
         else
            fileName.AppendFormat(".{0:x2}{1:x2}{2:x2}", BackColor.R, 
                                                         BackColor.G, BackColor.B);
         break;
      case Corners.BottomLeft:
         fileName.Append("bl");

         // now add the backcolor
         fileName.AppendFormat(".{0:x2}{1:x2}{2:x2}", BackColor.R, 
                                                        BackColor.G, BackColor.B);
         break;
      case Corners.BottomRight:
         fileName.Append("br");

         // now add the backcolor
         fileName.AppendFormat(".{0:x2}{1:x2}{2:x2}", BackColor.R, 
                                                        BackColor.G, BackColor.B);
         break;
   }

   // now add in the borderColor
   fileName.AppendFormat(".{0:x2}{1:x2}{2:x2}.", BorderColor.R, 
                                                     BorderColor.G, BorderColor.B);

   // add in the borderStyle
   fileName.Append(BorderStyle.ToString());

   // add in the corner width and height
   fileName.AppendFormat(".{0}-{1}", CornerWidth, CornerHeight);

   // finally add in the borderWidth
   fileName.Append(".").Append(BorderWidth.ToString()).Append(".gif");

   // if file doesn't exist, dynamically create the appropriate image
   if (!File.Exists(HttpContext.Current.Server.MapPath(fileName.ToString())))
   {
      // file does not exist - create it
      CreateRoundedCorner(c, fileName.ToString());
   }

   // Create an Image with the right filename and return it
   System.Web.UI.WebControls.Image img = new System.Web.UI.WebControls.Image();
   img.BorderWidth = Unit.Empty;
   img.ImageUrl = fileName.ToString();
   img.Height = CornerHeight;
   img.Width = CornerWidth;

   return img;
}

The filename is composed of two characters specifying what corner the image is for (upper-left, upper-right, ...), the HTML color value of its background color, the HTML color value of its border color, its border style, the height and width of the corner image, and the border width. For example, the image for a 13px by 13px upper-left corner with a blue background and a solid, black, 1px border would be: ul.0000ff.000000.Solid.13-13.1px.gif. A long filename, yes, but it uniquely identifies the corner image by utilizing its defining characteristics. This way, if the page developer changes the background color to white and the border width to 2px, the filename will change appropriately to: ul.000000.000000.Solid.13-13.2px.gif. (The filename is prepended with the value of the ImageDirectory property as well, as you can see in the initialization of the StringBuilder in the first line of the above method.)

After the filename has been determined, CreateCorner() checks to see if the file exists. If not, it calls CreateRoundedCorner(), which dynamically creates the image and saves it to the specified file path. Finally, a new Image Web control is instantiated and its ImageUrl property is set to the appropriate file path.

Dynamically Generating the Rounded Corner Image


Creating images of the fly in an ASP.NET Web page is not terribly difficult thanks to the GDI+ library. GDI+ is the term used to describe a set of classes in the .NET Framework for creating and manipulating images. Past articles on 4Guys - such as True Image Resizing, A Robust Image Gallery for ASP.NET, and Creating Snazzy Web Charts and Graphics On the Fly with ASP.NET - have shown how to utilize the GDI+ libraries to accomplish common image-related tasks.

The main challenge in creating or manipulating images with GDI+ is making those images look good. Since the corner images created for RoundedCorners are GIFs, I'll focus on the GDI+ issues with GIF files. When you create or open an image with the GDI+ classes, the classes use 32 bits per pixel, When you save a GIF file with GDI+, GDI+ dithers the image, reducing the palette to 256 Web-safe colors. The problem is that not all colors in the GIF will be accurately represented, which leads to a grainy looking image. The solution to overcoming grainy GIF images is to use quantization, which is the process of adjusting the GIF's palette so that it more closely matches the image's true colors, thereby resulting in a less grainy image.

Admittedly, I am no expert on image file formats and related algorithms, but fortunately there's a good article on MSDN by Morgan Skinner called Optimizing Color Quantization for ASP.NET Images, which looks at the problem in more details and provides a set of classes for performing quantization. The RoundedCorners Web control utilizes Morgan's Palette-based Quantization algorithm to "clean up" the graininess of the GIF image. Too, I modified the algorithm slightly so that the first palette color is the transparent color. This transparent color is used as the background of the corner images, so that if the rounded corner box is placed on a colored background, the background color shows through the area outside of the border of the rounded corner. (Specifically, in the GetPalette() method in PaletteQuantizer.cs I added palette.Entries[0] = Color.FromArgb(0, _colors[0]);, which sets the first palette item to transparent (that's what the 0 passed into FromArgb() indicates). In CreateRoundedCorner() I define the palette's entries, and make sure to cover the image with the first palette entry color initially, before adding the curve of the rounded corner.)

Conclusion


In this article we looked at RoundedCorners, a custom Web control I created to display a box with rounded corners without having to create any corner image files. RoundedCorners works by generating a 3x3 HTML <table>, with rounded corner images in each corner. These images are created on the fly, if necessary, and then saved to the Web server's file system so that they can be reused without having to be regenerated. To improve the quality of the image, I utilize Morgan Skinner's quantization routines, and tweaked them ever so slightly to make the first palette color the transparent one. The complete source code is downloadable at the end of this article.

Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the complete code (in ZIP format)
  • View a live demo
  • The impetus for this control is due in part to two individuals: Paul Murphy, who on a listserv I'm on asked about if there were any way to create a rounded corners box without having to manually create a file, and then suggesting that a Web control be created that could do as such; and Pierre Huguet, who gave a talk on July 20, 2004 at the San Diego ASP.NET SIG about GDI+.

    More Information About this Control!
    A new article has been written with more information about this Web control. Once you have read this article, be sure to read Improving the RoundedCorners Web Control.



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