Tuesday, July 17, 2007

Rounded Panel without Images

Panels with rounded borders are widely used in the web site design. So widely that it is hard to add something new. I just try to wrap existing ideas in the form of a web control.

Features:
  • Can be set radius of the circle, back color and border color
  • Without images (pure HTML)
  • Only 130 lines of code
  • Easy to use (see examples below this article)
<cc:RoundedPanel ID="RoundedPanel1" runat="server" CssClass="r1" BorderColor="Gold" BackColor="Beige" Radius="9"></cc:RoundedPanel>

I inherit control from WebControl. Mainly it will be a container for another elements so it needs PersistChildrenAttribute and ParseChildrenAttribute to be set correctly. BackColor and BorderColor properties have been defined in the base class, only radius of circle has to be added.
 [PersistChildren(true), 
ParseChildren(false),
ToolboxData("<{0}:RoundedPanel runat=\"server\"></{0}:RoundedPanel>"),
Designer(typeof(System.Web.UI.Design.ContainerControlDesigner ))]
public class RoundedPanel : WebControl
{
[Browsable(true), DefaultValue(5), Category("Appearance")]
public int Radius
{
get
{
return (int)(ViewState["Radius"] ?? 5);
}
set
{
ViewState["Radius"] = value;
}
}
...

Here is my favorite site where I am looking for examples of the use of CSS in real life. I'd like to take one of those examples of boxes with rounded corners as a base for web control but unfortunately none of the implementation on the basis of DIV cannot properly stretch if inner content is larger then the given DIV width. But in the development of the sites with dynamically loadable content, this problem is not that it would be everywhere but rather predictable.


So I decide to take as a basis a TABLE element - don't judge severely, I am developer not web designer :).

The corners are made of blocks (B elements) of varying widths and with different borders (look at the picture). B element are taken taken on the sole reason that its name is shorter than DIV. And inasmuch as corners are built from n elements (n = radius), that gives some savings of HTML.
public override void RenderControl(HtmlTextWriter writer)
{
//forms four corners
StringBuilder sbTopLeft = new StringBuilder();
StringBuilder sbTopRight = new StringBuilder();
StringBuilder sbBottomLeft = new StringBuilder();
StringBuilder sbBottomRight = new StringBuilder();
for (int i = 0; i < Radius; i++)
{
sbTopLeft.AppendFormat("<b class='a{0}'></b>", i);
sbTopRight.AppendFormat("<b class='b{0}'></b>", i);
sbBottomLeft.AppendFormat("<b class='a{0}'></b>", Radius - i - 1);
sbBottomRight.AppendFormat("<b class='b{0}'></b>", Radius - i - 1);
}
//renders upper part of the panel
writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0");
writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");
writer.AddAttribute(HtmlTextWriterAttribute.Class, CssClass);
writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
if (!Width.IsEmpty)
writer.AddStyleAttribute(HtmlTextWriterStyle.Width, Width.ToString());
if (!Height.IsEmpty)
writer.AddStyleAttribute(HtmlTextWriterStyle.Height, Height.ToString());
writer.RenderBeginTag("table");
writer.Write(string.Format(@"<tr>
<td class='l' align=right>{0}</td>
<td class='m mt'> </td>
<td class='r'>{1}</td>
</tr>

<tr>
<td class='m ml'> </td>
<td class='m'>", sbTopLeft, sbTopRight));
//renders child controls
base.RenderContents(writer);
//renders bottom part of the panel
writer.Write(string.Format(@"</td>

<td class='m mr'> </td>
</tr>
<tr>
<td class='l' align=right>{0}</td>
<td class='m mb'> </td>
<td class='r'>{1}</td>

</tr>", sbBottomLeft, sbBottomRight));
writer.RenderEndTag();
}

Then I create styles based on selected colors and desired circle radius. They make all work.
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//I register styles using RegisterClientScriptBlock because it hard to get desired style names by means
//of proper function Page.Header.StyleSheet.RegisterStyle. Also if there are a few same panels on a page
//the styles will be registered just once.

if (!Page.ClientScript.IsClientScriptBlockRegistered(typeof(Page), CssClass))
{
//gets color name in either "red" or "#f5d724" form
string backColorStr = BackColor.IsNamedColor ? BackColor.Name : "#"+BackColor.Name.Substring(2);
string borderColorStr = BorderColor.IsNamedColor?BorderColor.Name:"#"+BorderColor.Name.Substring(2);
StringBuilder sb = new StringBuilder();
sb.AppendLine("<style type='text/css'>");
sb.AppendFormat(@".{0} .l b,.{0} .r b(background-color:{1};height:1px;overflow:hidden;display:block;)
.{0} .ml(border-left:1px solid {2};)
.{0} .mr(border-right:1px solid {2};)
.{0} .m(background-color:{1};)
.{0} .mt(border-top:1px solid {2};height:{4}px;font-size:1px;)
.{0} .ml,.{0} .mr(width:{3}px;font-size:1px;)
.{0} .mb(border-bottom:1px solid {2};height:{4}px;font-size:1px;)",
CssClass, backColorStr, borderColorStr, Radius, Radius - 1);
int prevWidth = -1;

//particular implementation for Internet Explorer is required if quirks mode is used.
bool isIE = HttpContext.Current.Request.Browser.Browser.Equals("IE");
for (int i = 0; i < Radius; i++)
{
double angle = Math.Asin((double)(Radius - i) / Radius);
int width = (int)Math.Ceiling((Radius * Math.Cos(angle)));
if (width - prevWidth < 2)
sb.AppendFormat(".{0} .a{1}(width:{2}px;border-left:solid 1px {3})",
CssClass, i, width, borderColorStr);
else
{
if (isIE)
sb.AppendFormat(".{0} .a{1}(width:{2}px;border-left:solid {3}px {4})",
CssClass, i, width, (width - prevWidth), borderColorStr);
else
sb.AppendFormat(".{0} .a{1}(width:{2}px;border-left:solid {3}px {4})",
CssClass, i, (1 + prevWidth), (width - prevWidth), borderColorStr);
}
if (width - prevWidth < 2)
sb.AppendFormat(".{0} .b{1}(width:{2}px;border-right:solid 1px {3})",
CssClass, i, width, borderColorStr);
else
{
if (isIE)
sb.AppendFormat(".{0} .b{1}(width:{2}px;border-right:solid {3}px {4})",
CssClass, i, width, (width - prevWidth), borderColorStr);
else
sb.AppendFormat(".{0} .b{1}(width:{2}px;border-right:solid {3}px {4})",
CssClass, i, (1 + prevWidth), (width - prevWidth), borderColorStr);
}
prevWidth = width;
}
sb.Append("\r\n</style>");
string styles = sb.ToString().Replace("(", "{").Replace(")", "}");
Page.ClientScript.RegisterClientScriptBlock(typeof(Page), CssClass, styles);
}
}

This is implementation in case of your page is processed as HTML (<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >). If the page is processed as XHTML (<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">) then remove the condition for Internet Explorer.

Source Code, 1.6 Kb

No comments:

Post a Comment