Creating a Vista-like Menu Bar Custom Control

I came across an excellent post on the Itookia website that described creating a Aero-glass like menu bar (from Vista) using just three images and CSS. Like most cool web components, I have been on a kick lately to find a way to encapsulate the functionality into an ASP.NET server control.

Note: The original CSS source and descriptions are available on the Itookia site. Thanks to them for the cool CSS. The control was built using .NET 3.5; however, does not contain any 3.5–specific libraries. For convenience, I’ve provided both the 2.0 and 3.5 projects.

To begin, take a look at the aforementioned site and familiarize yourself with the CSS and what’s going on. Even run the demo. It’s very simple—not even a scrap of JavaScript on the page.

There are four attributes to a menu item, the Navigation URL (string), the Image URL (string), the Display Text (string) , and whether or not to right align the item (bool).

Embedding Resources

This control will need those three basic image files and a style sheet for operation. The best way to handle this is to embed the resources into the control—rather than relying on the user to have the files in the correct place.

To embed the resources (and create an Embedded Resource…):

Add the files to your project. If you look at the image above, you’ll see a folder called VistaMenuBar with the files inside.

Set the Build Action property of each of the files to “Embedded Resource”.

Instead of a standard WebControl, this control will need to inherit from the DataBoundControl base class. This allows us to use DataSource and grants access to the PerformingDataBinding method.

publicclassVistaMenuBar : DataBoundControl { }

Adding public properties. Next, add four public properties to hold the four attributes previously discussed. In this example, they’re named NavigateUrl, ImageUrl, Text, and AlignRight and each are Bindable.

In addition, set the default value to match the name of the property—this eases the use of the MenuItem object we’ll create later.

Here’s an example of the NavigateUrl property.

[Bindable(true)]

[Category(“Appearance”)]

[DefaultValue(“NavigateUrl”)]

[Localizable(true)]

publicstring NavigateUrl

{

get

{

String s = (String)ViewState[“NavigateUrl”];

return ((s == null) ? “NavigateUrl” : s);

}

set

{

ViewState[“NavigateUrl”] = value;

}

}

Overriding methods. Our first method to override is the OnPreRender method. Remember the embedded CSS file we added, we need to populate that on our page BEFORE the control loads.

protectedoverridevoid OnPreRender(EventArgs e)

{

base.OnPreRender(e);

string includeLocation =

Page.ClientScript.GetWebResourceUrl(this.GetType(),

“TSCustomControls.VistaMenuBar.VistaMenuBar.css”);

HtmlLink cssLink = newHtmlLink();

cssLink.Href = includeLocation;

cssLink.Attributes.Add(“rel”, “stylesheet”);

cssLink.Attributes.Add(“type”, “text/css”);

this.Page.Header.Controls.Add(cssLink);

}

This code first runs all base OnPreRender methods, then grabs the Embedded Resource’s URL (handed from WebResource.axd) and pushes a style tag into the header of the page. The resultant HTML looks something like:

Please memorize that URL—there will be a quiz later (maybe). If you’re unfamiliar with Embedded Resources, do some Googling—it’s a great way to keep files attached to objects and such.

The second method does the heavy lifting. The PerformDataBinding method accepts an IEnumerable data source and allows you the freedom to control what happens with the data. For this control, we want to generate simple HTML.

protectedoverridevoid PerformDataBinding(IEnumerable retrievedData)

{

base.PerformDataBinding(retrievedData);

if (this.DataSource != null)

{

Literal literal = newLiteral();

StringBuilder output = newStringBuilder();

output.AppendLine(“<div id=’vista_toolbar’>”);

output.AppendLine(“<ul>”);

foreach (object dataItem in retrievedData)

{

PropertyDescriptorCollection props =

TypeDescriptor.GetProperties(dataItem);

string navigateUrl =

props[this.NavigateUrl].GetValue(dataItem).ToString();

string imageUrl =

props[this.ImageUrl].GetValue(dataItem).ToString();

string text =

props[this.Text].GetValue(dataItem).ToString();

bool alignRight =

Convert.ToBoolean(props[this.AlignRight].GetValue(dataItem));

if (alignRight)

{

output.AppendFormat(“<li><a class=’right’ href='{0}’><span>”,

navigateUrl);

}

else

{

output.AppendFormat(“<li><a href='{0}’><span>”, navigateUrl);

}

output.AppendLine();

if (imageUrl != “”)

{

output.AppendFormat(“<img align=’left’ src='{0}’ alt='{1}’ />”,

imageUrl, text);

}

output.AppendFormat(“{0}</span></a></li>”, text);

}

output.AppendLine(“</ul>”);

output.AppendLine(“</div>”);

literal.Text = output.ToString();

this.Controls.Add(literal);

}

}

Woah—lots of code. It’s actually not too bad, just a bit more to deal with the boxing of the object. Here’s what it does:

Process the DataBinding.

Verify that the DataSource isn’t null—if it is, skip all this work.

Create a Literal object and a StringBuilder object—that’s where we’ll put our HTML.

For each “data item” object in the IEnumerable collection, loop through.

Reflect into the “data item” object and get it’s properties. Set those properties to strings and a boolean. This could be done inline down below—but, it just seems messier that way.

The last piece is totally optional. I created a VistaMenuItem object to allow me to pass in a generic List of VistaMenuItems. Here’s the structure; more on how to use it when we start consuming the control.

publicclassVistaMenuItem

{

publicstring Text { get; set; }

publicstring ImageUrl { get; set; }

publicstring NavigateUrl { get; set; }

publicbool AlignRight { get; set; }

public VistaMenuItem(string text,

string imageUrl,

string navigateUrl, bool alignRight)

{

Text = text;

ImageUrl = imageUrl;

NavigateUrl = navigateUrl;

AlignRight = alignRight;

}

}

Using the Control

On your ASPX form, compile your new control and add it to the page.

<cc1:VistaMenuBarID=”VistaMenuBar1″runat=”server”Width=”100%” />

We have two ways we can populate the menu items: pass an IEnumerable DataSource or pass an IEnumerable list of VistaMenuItem objects.

The difference? The VistaMenuItems already know what value goes where in the menu. The DataSource (a DataTable, etc) will require you to specify the NavigateUrl, ImageUrl, Text, and AlignRight properties if your columns do not match the attribute names. If they do match, it will pre-fill.

Using a quick DataTable as an example, let’s see what we get. Now, this is just an example—for “manual entry”, I’d suggest using the VistaMenuItems. The best way, of course, would be to simply provide a DataSourc that’s pre-populated.

DataTable dt = newDataTable();

dt.Columns.Add(“MyNavigateUrl”, typeof(string));

dt.Columns.Add(“MyImageUrl”, typeof(string));

dt.Columns.Add(“MyText”, typeof(string));

dt.Columns.Add(“MyAlignRight”, typeof(bool));

dt.Rows.Add(“Default.aspx”, “VistaMenuBar/mona.gif”,

“Home”, false);

dt.Rows.Add(“Default.aspx”,“VistaMenuBar/chart.gif”,

“Add a Chart”, false);

dt.Rows.Add(“Default.aspx”, “VistaMenuBar/sos.gif”,

“Get Help”, false);

dt.Rows.Add(“Default.aspx”, “VistaMenuBar/mail.gif”,

“Send Email”, false);

VistaMenuBar1.DataSource = dt;

VistaMenuBar1.NavigateUrl = “MyNavigateUrl”;

VistaMenuBar1.ImageUrl = “MyImageUrl”;

VistaMenuBar1.Text = “MyText”;

VistaMenuBar1.AlignRight = “MyAlignRight”;

VistaMenuBar1.DataBind();

We can also use the list of VistaMenuItems and bypass setting those properties. It’s up to you!