an ASP.NET, C# technical blog, by Gianni Tropiano

How to code a reusable round corner box

by CodeGolem 27. September 2009 13:46

Article of the day at ASP.NET (october 9th, 2009)

UPDATE: As user Christian Schiffer suggested, I've posted an updated version to get transparent corners.

 

FULL SOURCE CODE IS AVAILABLE AT THE BOTTOM OF THIS ARTICLE.

Most of us already use round-corners to give a more attractive look to our web sites.

What about creating a reusable control, with dynamically generated corner images we can easily skin with themes?

In a previous post I already suggested a way to create a reusable dynamic text-generating control. Here we will use the same technique: a custom HttpHandler to dynamically render the corner images.

We will use CSS absolute positioning to place the rounded corners on top of the square ones, hence giving the illusion of a round corner box.

This is a sample of how the box's elements will be composed: 

But let's start looking at the corner-generating HttpHandler.

Each corner will be rendered as a filled ellipse. All needed parameters need to be passed to the HttpHandler as QueryString parameters.

Here is the HttpHandler's ProcessRequest override implementation, with private properties mapping each QueryString parameter to determine: the corner radius, the border width and color, the back and fore colors, the corner position (top-left, top-right, bottom-left or bottom-right).


public void ProcessRequest(HttpContext context)
{
    // instanciate a new image with needed size
    Bitmap image = new Bitmap(_cornerRadius, _cornerRadius);

    // instanciate a graphics object for drawing on the image
    Graphics graph = Graphics.FromImage(image);
    graph.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

    // fill the whole image with the background color
    graph.Clear(_backColor);

    // instanciate a pen object with the border's color
    Pen pen = new Pen(_borderColor, _borderWidth);

    // determine ellipse's coordinates
    // based on the requested corner position
    int startX = 0, startY = 0;
    switch (_cornerPosition)
    {
        case CornerPositionEnum.TopLeft:
            startX = 0;
            startY = 0;
            break;
        case CornerPositionEnum.TopRight:
            startX = -_cornerRadius;
            startY = 0;
            break;
        case CornerPositionEnum.BottomLeft:
            startX = 0;
            startY = -_cornerRadius;
            break;
        case CornerPositionEnum.BottomRight:
            startX = -_cornerRadius;
            startY = -_cornerRadius;
            break;
    }

    // draw the ellipse
    graph.FillEllipse(new SolidBrush(_foreColor), startX, startY, _cornerRadius * 2 - _borderWidth, _cornerRadius * 2 - _borderWidth);
    graph.DrawEllipse(pen, startX, startY, _cornerRadius * 2 - _borderWidth, _cornerRadius * 2 - _borderWidth);

    // dispose the graphics object
    graph.Dispose();

    // instanciate an encoder with quality parameters
    // to save the image as 100% quality JPEG file
    EncoderParameter encoderParam = new EncoderParameter(Encoder.Quality, 100L);
    EncoderParameters encoderParams = new EncoderParameters();
    encoderParams.Param[0] = encoderParam;

    ImageCodecInfo jpegCodec = null;
    ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();

    for (int i1 = 0; i1 < codecs.Length; i1++)
    {
        if (codecs[i1].FormatDescription == "JPEG")
        {
            jpegCodec = codecs[i1];
            break;
        }
    }

    // output to the response stream
    image.Save(context.Response.OutputStream, jpegCodec, encoderParams);

    // dispose the image object
    image.Dispose();
}

 As usual, the HttpHandler needs to be registered in the web.config file:


<system.web>
    <httpHandlers>
        <add verb="GET" path="CornerImageHandler.ashx" type="CodeGolem.CornerImageHandler"/>
    </httpHandlers>
</system.web>

<!-- IIS 7 -->
<system.webServer>
    <handlers>
        <add name="CornerImageHandler" verb="GET" path="CornerImageHandler.ashx" type="CodeGolem.CornerImageHandler"/>
    </handlers>
</system.webServer>

Now we need a Custom Control to render the whole html for us. I have implemented this as a Control inherited from the Panel class.

The RoundCornerPanel inherits all properties from the Panel class, and declares the following additional ones:


/// <summary>
/// Color of the box's background
/// </summary>
public Color OutColor
{
    get;
    set;
}

/// <summary>
/// Corners' radius, in pixels
/// </summary>
public int CornerRadius
{
    get;
    set;
}

And here is the RoundCornerPanel's Render override implementation:


protected override void Render(HtmlTextWriter writer)
{
    // start rendering the control
    RenderBeginTag(writer);

    // add a relative-positioned div
    writer.WriteLine("<div style='position: relative; width: 100%; height: 100%; left: 0px; top: 0px;'>");

    // create div tags for each corner
    writer.WriteLine("<div style='{0}'></div>", getCornerStyle(CornerPositionEnum.TopLeft));
     writer.WriteLine("<div style='{0}'></div>", getCornerStyle(CornerPositionEnum.TopRight));
     writer.WriteLine("<div style='{0}'></div>", getCornerStyle(CornerPositionEnum.BottomLeft));
     writer.WriteLine("<div style='{0}'></div>", getCornerStyle(CornerPositionEnum.BottomRight));

    // create a content div with a customizable 'Content' CSS class
    writer.WriteLine("<div class='Content' style='position:absolute; top: 0px; left: 0px; z-index: 2; width: 100%; height: 100%'>");

    // render the contents of the panel
    RenderContents(writer);
    
    // close the inner panels
    writer.WriteLine("</div></div>");

    // render the closing tag
    RenderEndTag(writer);
}

Note in bold:

  1. Each corner is rendered as a separate absolute-positioned div
  2. I've added a div with 'Content' CSS class name as a container for child controls. This will allow us to declare a custom CSS class in our website to keep control over the content's layout.

The absolute-positioning CSS style is rendered within the getCornerStyle() method:


private string getCornerStyle(CornerPositionEnum cornerPosition)
{
    Unit top, left, marginTop, marginLeft;
    top = left = marginTop = marginLeft = Unit.Pixel(0);
    int borderWidth = (int)BorderWidth.Value;

    switch (cornerPosition)
    {
        case CornerPositionEnum.TopLeft:
            top = Unit.Pixel(-borderWidth);
            left = Unit.Pixel(-borderWidth);
            break;
        case CornerPositionEnum.TopRight:
            top = Unit.Pixel(-borderWidth);
            left = Unit.Percentage(100);
            marginLeft = Unit.Pixel(borderWidth - CornerRadius);
            break;
        case CornerPositionEnum.BottomLeft:
            top = Unit.Percentage(100);
            marginTop = Unit.Pixel(borderWidth - CornerRadius);
            left = Unit.Pixel(-borderWidth);
            break;
        case CornerPositionEnum.BottomRight:
            top = Unit.Percentage(100);
            marginTop = Unit.Pixel(borderWidth - CornerRadius);
            left = Unit.Percentage(100);
            marginLeft = Unit.Pixel(borderWidth - CornerRadius);
            break;
    }

    return string.Format("position:absolute;z-index:1;width:{0}px;height:{0}px;top:{1};left:{2};margin-top:{3};margin-left:{4};background-image:url(CornerImageHandler.ashx?radius={0}&width={5}&color={6}&fcolor={7}&bcolor={8}&pos={9})",
        CornerRadius,
        top,
        left,
        marginTop,
        marginLeft,
        borderWidth,
        ColorTranslator.ToHtml(BorderColor),
        ColorTranslator.ToHtml(BackColor),
        ColorTranslator.ToHtml(OutColor),
        (int)cornerPosition);
}

Finally, here is a sample page using the RoundCornerPanel control:


<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register TagPrefix="CodeGolem" Namespace="CodeGolem" Assembly="RoundCornerPanel" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Round Corner Panel Sample</title>
    <style type="text/css">
    .RoundBox .Content
    {
        padding: 10px;
        font-family: Verdana;
        font-weight: bold;
    }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <br />
        <CodeGolem:RoundCornerPanel
            runat="server"
            OutColor="White"
            BackColor="Yellow"
            ForeColor="Black"
            BorderColor="Red"
            CornerRadius="20"
            BorderWidth="2"
            Width="200"
            Height="200"
            CssClass="RoundBox">
            Test me!
        </CodeGolem:RoundCornerPanel>
    </form>
</body>
</html>

This will render something like the following sample:

excuse me for poor color choice... Embarassed

Note:

  1. The control needs to be registered in the page, or in the web.config
  2. A custom RoundBox Content CSS class is declared and assigned to the RoundCornerPanel control

Finally, here is full source package:

CodeGolem_RoundCornerPanel.zip (16 kb)

Hope you will find this useful.

Happy round-boxing! Smile

 

Tags: ,

ASP.NET