Skip to main content

Dynamic Rounded Panel

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
if (styleGroupName == null)
string key = string.Format("{0}{1}{2}{3}",
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(
//creates styles
StringBuilder sb = new StringBuilder();
@".{0} .a, .{0} .a b, .{0} .d, .{0} .d b
.{0} .a, .{0} .d, .{0} .b, .{0} .e
.{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.Radius - this.BorderWidth.Value,
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)
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());

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
@"<div class='{0}'><b class='a'><b class='b'><b class='c'><b></b></b></b></b><div class='m'>",
//renders inner controls

//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>");

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);
Marshal.WriteByte(destData.Scan0, destData.Stride * y + x, 0);
//unlocks the images
//writes the indexed image to the Response
context.Response.ContentType = "image/gif";
dest.Save(context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Gif);

In the end it is required to register in web.config HttpHandlers that are responsible for drawing the image and the stylesheet registration.
<add verb="GET" path="roundedpanel.ashx" type="MyAssembly.MyNamespace.RoundedPanel, MyAssembly"/>
<add verb="GET" path="stylesheet.css" type="MyAssembly.MyNamespace.StylesController, MyAssembly"/>

Source code - 3.5 kB


Popular posts from this blog

How to merge cells with equal values in the GridView

My solution is not the first; however, I think, it is rather universal and very short - less than 20 lines of the code.

The algorithm is simple: to bypass all the rows, starting from the second at the bottom, to the top. If a cell value is the same as a value in the previous (lower) row, then increase RowSpan and make the lower cell invisible, and so forth.

The code that merges the cells is very short:
public class GridDecorator { public static void MergeRows(GridView gridView) { for (int rowIndex = gridView.Rows.Count - 2; rowIndex >= 0; rowIndex--) { GridViewRow row = gridView.Rows[rowIndex]; GridViewRow previousRow = gridView.Rows[rowIndex + 1]; for (int i = 0; i < row.Cells.Count; i++) { if (row.Cells[i].Text == previousRow.Cells[i].Text) { row.Cells[i].RowSpan = previousRow.Cells[i].RowSpan < 2 ? 2 : prev…

Merging columns in GridView/DataGrid header

As necessity to show header columns in a few rows occurs fairly often it would be good to have such functionality in the GridView/DataGrid control as an in-built feature. But meanwhile everyone solves this problem in his own way.

The described below variant of the merging implementation is based on irwansyah's idea to use the SetRenderMethodDelegate method for custom rendering of grid columns header. I guess this approach can be simplified in order to get more compact and handy code for reuse.
The code overview
As it may be required to merge a few groups of columns - for example, 1,2 and 4,5,6 - we need a class to store common information about all united columns.
private class MergedColumnsInfo
// indexes of merged columns
public List<int> MergedColumns = new List<int>();
// key-value pairs: key = the first column index, value = number of the merged columns
public Hashtable StartColumns = new Hashtable();
// key-value pairs: key = the first column in…

JIRA REST API: Cookie-based Authentication

Three authentication methods are proposed by the JIRA REST API documentation: Basic Authentication is a simple but not very safe approach. Credentials are sent in the header on every request and encoding to Base64 is not a proper protection in this case; HTTPS connection is required. OAuth authentication - looks a bit complex and requires additional configuration at the JIRA server that is not always possible. Cookie-based Authentication - this approach seems to be the most convinient one: credentials are checked once, then the authentication cookie only is sent on subsequent requests. However, trying to use the cookie-based authentication I encountered an issue. The approach described in the documentation worked partially: I was able to create a new session and get the response containing the session cookie but all subsequent requests using this session cookie were rejected as unauthorized. Spending some time investigating this I found the cause of the issue: JSESSIONID is not th…