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.
Besides, the control implements interface
Generation and registration of the styles that are required for rendering the panel occur in the overriden
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.NetPanel
, one property is added:Radius
- the circle radiusBesides, 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
Post a Comment