Skip to main content

Dynamic Rounded Panel

Dynamic Rounded Panel (test sample)

Width, px Height, px
Radius of circle, px Border Width, px
Background Color Border Color
Apply
inner text

Dynamic Rounded Panel

14 Nov 2007 (updated 10 Jun 2008) This is another variant of the rounded panel creation (the first one you can see here), that uses images to present curved corners. To be more precise, the only image is used, the one that is build based on the specified circle radius, the background color, the border color and width. The panel appearance varies according to all these parameters and can be dynamically changed in runtime.

Picture 1. The structure of the top part of the rounded panel
a - the left part where the upper left part of the image is shown.
b - the middle that is filled with the background color and contains a block element (d). The d element imitates the image border.
c - the right part where the upper right part of the image is shown. Of course, the middle can be simpler if to use the only block that is filled with the background color and has the upper border. But different browsers interpret borders ambiguously, some of them enlarge total height of the block element. It caused a small complication of the control.
The bottom part of border is build on the same principle, only mirror-like.

The code overview

The rounded panel is inherited from the ordinary ASP.Net Panel, one property is added:
Radius - the circle radius
Besides, the control implements interface IHttpHandler and contains a code for the image creation. It has to be registered in web.config in order to process requests for the ~/roundedpanel.ashx?... url.
Generation and registration of the styles that are required for rendering the panel occur in the overriden OnPreRender method.
private string styleGroupName = null;
//the name for the styles group that is responsible for the panel representation
private string StyleGroupName
        {
            get
            {
                if (styleGroupName == null)
                {
                    string key = string.Format("{0}{1}{2}{3}", 
                                                        this.BorderColor.ToArgb(), 
                                                        this.BackColor.ToArgb(), 
                                                        this.BorderWidth, 
                                                        this.Radius);
                    byte[] bytes = System.Text.Encoding.UTF8.GetBytes(key);
                    styleGroupName = Convert.ToBase64String(bytes).Replace("=", "");
                }
                return styleGroupName;
            }
}

//query key for the circle radius
private const string radiusQueryKey = "rd";
//query key for the border width
private const string borderWidthQueryKey = "bw";
//query key for the border color
private const string borderColorQueryKey = "bc";
//query key for the background color
private const string backgroundColorQueryKey = "bg";

protected override void OnPreRender(EventArgs e)
{
    if (this.Radius > 0)
    {
        //creates url to the image showing the curved corners
        string imageUrl = string.Format(
           "~/roundedpanel.ashx?{0}={1}&{2}={3}&{4}={5},{6},{7}&{8}={9},{10},{11}",
            radiusQueryKey,
            this.Radius,
            borderWidthQueryKey,
            this.BorderWidth.Value,
            borderColorQueryKey,
            this.BorderColor.R,
            this.BorderColor.G,
            this.BorderColor.B,
            backgroundColorQueryKey,
            this.BackColor.R,
            this.BackColor.G,
            this.BackColor.B);
        //creates styles
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat(
@".{0} .a, .{0} .a b, .{0} .d, .{0} .d b 
{{display:block;font-size:1px;overflow:hidden;}}
.{0} .a, .{0} .d, .{0} .b, .{0} .e 
{{background-image:url({4});background-repeat:no-repeat;height:{1}px;}}
.{0} .b, .{0} .e {{margin-left:{1}px;}}
.{0} .a {{background-position:left top;}}
.{0} .b {{background-position:right top;}}
.{0} .d {{background-position:left bottom;}}
.{0} .e {{background-position:right bottom;}}
.{0} .c, .{0} .f {{background-color:{5};height:{1}px;margin-right:{1}px;}}
.{0} .c b, .{0} .f b {{height:{2}px;background-color:{6};}}
.{0} .f b {{margin-top:{3}px;}}
.{0} .m {{background-color:{5};border-left:solid {2}px {6};border-right:solid {2}px {6};}}",
this.StyleGroupName,
this.Radius,
this.BorderWidth.Value,
this.Radius - this.BorderWidth.Value,
ResolveUrl(imageUrl),
this.BackColor.IsNamedColor ? this.BackColor.Name : "#" + this.BackColor.Name.Substring(2),
this.BorderColor.IsNamedColor ? this.BorderColor.Name : "#" + this.BorderColor.Name.Substring(2));
        //adds styles for the panel width, if it is required
        if (!this.Width.IsEmpty)
            sb.AppendLine(string.Format(".{0}{{width:{1}}}",
                this.StyleGroupName, this.Width));
        
        //adds styles for the panel height, if it is required
        if (!this.Height.IsEmpty && this.Height.Type == UnitType.Pixel)
            sb.AppendLine(string.Format(".{0} .m{{height:{1}px;overflow:visible}}",
                this.StyleGroupName, this.Height.Value - this.Radius * 2));
        //registers styles on the page
        if (!StylesController.IsStyleSheetIncludeRegistered(this.Page, "Rounded Panel"))
            StylesController.RegisterStyleSheetInclude(this.Page, "Rounded Panel", sb.ToString());
    }
    base.OnPreRender(e);
}
StylesController - a class that contains a few methods for registration of the styles created in runtime. It is described in the How to Register Stylesheet Created in Runtime article. Creation of HTML representing the control occurs in the overriden RenderControl method; the upper, bottom and middle parts are rendered separately.
public override void RenderControl(HtmlTextWriter writer)
{
    if (this.Radius > 0)
    {
        //renders the upper part of the control
        writer.Write(string.Format(
@"<div class='{0}'><b class='a'><b class='b'><b class='c'><b></b></b></b></b><div class='m'>", 
this.StyleGroupName));
        //renders inner controls
        base.RenderContents(writer);
        
        //renders the bottom part of the control
        writer.Write("</div><b class='d'><b class='e'><b class='f'><b></b></b></b></b></div>");
    }
    else
        base.RenderControl(writer);
}
The image representing curved corners is generated in the ProcessRequest method of the IHttpHandler implementation. It turns out to be the most complicated part of the work, as for me. The required image has to show a bordered circle filled with the specified background color, the rest of the image has to be transparent. It can be done by means of the transparent gif. My knowledge of GDI+ is rather superficial and examples found on the Internet are too complicated to simply copypaste. The Bob Powell's excellent resource on GDI+ and especially the article about peculiarities of the different images formats help me.
    Here is the way to draw partially transparent gif image for rounded corners:

  • First of all, you need to create a common bitmap in RGB format and draw the required image on its canvas. It is not possible to draw directly on gif-image because it contains indexed colors palette.
  • Create a gif-image of the same size that the image in RGB format.
  • Change color palette of the gif-image, it has to contain 3 colors: a transparent color, a border color and a background color.
  • Read colors pixel-by-pixel from the RGB-image and set colors for the corresponding images in the indexed image.
  • Save gif-image to the Response.
void IHttpHandler.ProcessRequest(HttpContext context)
{
    if (context.Request[radiusQueryKey] != null
        && context.Request[borderWidthQueryKey] != null
        && context.Request[borderColorQueryKey] != null
        && context.Request[backgroundColorQueryKey] != null)
    {
        int radius = int.Parse(context.Request[radiusQueryKey]);
        int borderWidth = int.Parse(context.Request[borderWidthQueryKey]);
        string[] args = context.Request[borderColorQueryKey].Split(new char[] { ',' });
        Color borderColor = Color.FromArgb(int.Parse(args[0]), int.Parse(args[1]), int.Parse(args[2]));
        args = context.Request[backgroundColorQueryKey].Split(new char[] { ',' });
        Color bgColor = Color.FromArgb(int.Parse(args[0]), int.Parse(args[1]), int.Parse(args[2]));
        //sets the transparent color
        Color transparentColor = Color.FromArgb(0, 0, 0, 0);
        //draws the image in the 32 bit RGB format
        Bitmap source = new Bitmap(radius * 2, radius * 2, PixelFormat.Format32bppRgb);
        Graphics g = Graphics.FromImage(source);
        g.FillRectangle(new SolidBrush(transparentColor), 0, 0, source.Width, source.Height);
        g.FillEllipse(new SolidBrush(bgColor), borderWidth / 2, borderWidth / 2, 
                source.Width - borderWidth, source.Height - borderWidth);
        g.DrawEllipse(new Pen(borderColor, borderWidth), 0 + borderWidth / 2, 0 + borderWidth / 2, 
                source.Width - borderWidth, source.Height - borderWidth);
        //creates the image in the indexed format
        Bitmap dest = new Bitmap(source.Width, source.Height, PixelFormat.Format8bppIndexed);
        
        //changes the color palette
        ColorPalette pal = dest.Palette;
        pal.Entries[0] = transparentColor;
        pal.Entries[1] = bgColor;
        pal.Entries[2] = borderColor;
        dest.Palette = pal;
        //sets a rectangle that occupies the whole picture area
        Rectangle rect = new Rectangle(0, 0, source.Width, source.Height);
        //locks the images in the memory
        BitmapData sourceData = source.LockBits(rect, ImageLockMode.ReadOnly, source.PixelFormat);
        BitmapData destData = dest.LockBits(rect, ImageLockMode.WriteOnly, dest.PixelFormat);
        for (int x = 0; x < source.Width; x++)
        {
            for (int y = 0; y < source.Height; y++)
            {
                //reads color of the pixel with coordinates x,y  from the source image
                Color color = Color.FromArgb(Marshal.ReadInt32(sourceData.Scan0, sourceData.Stride * y + x * 4));
                //sets color of the corresponding pixel in the destination image
                if (color == bgColor)
                    Marshal.WriteByte(destData.Scan0, destData.Stride * y + x, 1);
                else if (color == borderColor)
                    Marshal.WriteByte(destData.Scan0, destData.Stride * y + x, 2);
                else
                    Marshal.WriteByte(destData.Scan0, destData.Stride * y + x, 0);
            }
        }
        //unlocks the images
        dest.UnlockBits(destData);
        source.UnlockBits(sourceData);
        source.Dispose();
        //writes the indexed image to the Response
        context.Response.Clear();
        context.Response.ContentType = "image/gif";
        dest.Save(context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Gif);
        dest.Dispose();
        context.Response.End();
    }
}
In the end it is required to register in web.config HttpHandlers that are responsible for drawing the image and the stylesheet registration.
<httpHandlers>
 ...
 <add verb="GET" path="roundedpanel.ashx" type="MyAssembly.MyNamespace.RoundedPanel, MyAssembly"/>
 <add verb="GET" path="stylesheet.css" type="MyAssembly.MyNamespace.StylesController, MyAssembly"/>
</httpHandlers>
Source code - 3.5 kB

Comments

Popular posts from this blog

GRIDVIEW GROUPING

When displaying data, we sometimes would like to group data for better user experience or when displaying long list of hierarchal data, we would want to display them in a tree view kind of structure. There is more than way of doing this, but I am going to explain achieving this functionality using  AJAX Collapsible Panel Extender Control . Overview: I am going to use  Adventure Works  as datasource. Every product in  Production.Product  table belongs to a product sub category. We fetch handful of products and the sub categories they belong to from the database. Our objective is to list all the available sub categories and allow user to  expand/collapse  to look/hide the list of products belonging to each subcategory. Database Connection Added following entry under  connectionStrings  element in  web.config . < add   name = "Sql"   connectionString="Data Source=(local); Initial  Catalog = AdventureWorks ...

Dynamic Generation of Word Document Report in ASP.NET with HTML

Overview Ever needed to generate Word documents from your ASP.NET applications? There are a couple of components which will help to generate Word documents, but using these components may have some overhead such as Component Registration, setting permissions, licenses, etc., Sometimes, it may even become difficult to understand their features and use them. Generating word document in ASP.NET with HTML is incredibly easy. The following sample demonstrates how to place Heading and Table dynamically in the word document in ASP.NET web applications using HTML. Requirements Microsoft Visual Web Developer 2005 Express Edition Create the Web Application project Add the new Web Site Application project (with name as Generate Word Document) and place the button (with name as btnGenerateDocument) in the Default.aspx web form as shown below. Double click the highlighted code behind file from the Solution Explorer and add the following namespaces. Listing 1 using  System...

GRIDVIEW ZOOM IMAGE

When you have images in your  GridView , you would most likely show them as thumbnails so as to not distort the whole layout. However user would want to look at the full image by clicking on the image or just hovering his mouse over it. In today’s applications, this is a basic requirement and there are just so many third party controls or plugins which would support this functionality. I am going do this conventional way using  javascript  way in this article.  On top of it, I am also going to explain how to get images from database using  HttpHandlers . Example: I am using  Adventure Works  as datasource. We fetch handful of products and bind them to the grid. When page is initially loaded, we retrieve products from Production . Product  table and bind them to the grid. We display some product attributes such as Product ID, Product Number, Product Name, List Price and product’s thumbnail. When user hover his mouse on the page, we fetch the f...