Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Data Driven Tab Strip User Control

0.00/5 (No votes)
28 Oct 2003 2  
Reduce implementation time with a reusable, externally driven tab strip control.

Contents

Introduction

Recently, I've been getting my feet wet with ASP.NET and have been developing several user controls and form binding algorithms all driven by an external XSD/XML file pair.  This article presents one of these controls (the simplest actually), the TabStrip  The tab strip presented here is unmodified from the Internet Explorer WebControls, one of the collection of unsupported ASP.NET server side controls discussed here.  The only thing I've done is made data driven.

Why do this?  Because I realized rather quickly that:

  • I would get rather annoyed with having to define all those tabs every time I wanted a tab strip;
  • I also didn't want to be touching code whenever the tab strip changed during development;
  • writing single solution event handlers for each tab strip, and single solution code in general, is not my style.

What I Started With

What I started with was functional but ugly--a combination of HTML and ASP.NET links to define my "tab bar":

<form id="Form1" method="post" runat="server">
<asp:Table id="Table1" style="Z-INDEX: 101; LEFT: 10px; POSITION: relative;
    TOP: -12px" runat="server" Width="557px">
<asp:TableRow>
<asp:TableCell>
<a href="projects.aspx" target="main">Projects</a></asp:TableCell>
<asp:TableCell>
<a href="addDefect.aspx" target="main">Add</a></asp:TableCell>
<asp:TableCell>
<a href="updateDefect.aspx" target="main">Update</a></asp:TableCell>
<asp:TableCell>
<a href="searchDefect.aspx" target="main">Search</a></asp:TableCell>
<asp:TableCell>
<a href="reportDefect.aspx" target="main">Reports</a></asp:TableCell>
<asp:TableCell>
<a href="settings.aspx" target="main">Settings</a></asp:TableCell>
<asp:TableCell>
<asp:LinkButton ID="lbtnAdmin" Runat="server">Admin</asp:LinkButton>
</asp:TableCell>
<asp:TableCell>
<a href="help.aspx" target="main">Help</a></asp:TableCell>
<asp:TableCell>
<asp:LinkButton ID="lbtnLogout" Runat="server">Log Out</asp:LinkButton>
    </asp:TableCell>
</asp:TableRow>
</asp:Table>
</form>

Which looks like this:

What I Ended With

After incorporating the WebControls tabstrip, the HTML:

<%@ Page language="c#" Codebehind="tabStrip.aspx.cs" AutoEventWireup="false"
    Inherits="DHPoc.tabStrip" %>
<%@ Register TagPrefix="uc1" TagName="ucTabStrip" Src="ucTabStrip.ascx" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>tabStrip</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScript content="JavaScript">
<meta name=vs_targetSchema
    content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
<uc1:ucTabStrip id=UcTabStrip1 runat="server" TabStrip="Main"></uc1:ucTabStrip>
</form>
</body>
</HTML>

results in a nice looking tab strip (excluding the admin and log out buttons, which I kept as link buttons):

The User Control

The tab strip user control is responsible for two things:

  1. Dynamically generating the tab strip contents and attributes
  2. Responding to the event that fires when the user clicks on the control.

Dynamically Generating The Tab Strip Contents

First, we have to create a user control, which I called "ucTabStrip".  This user control is simply a stub:

<%@ Register TagPrefix="ie" Namespace="Microsoft.Web.UI.WebControls" 
    Assembly="Microsoft.Web.UI.WebControls" %>
<%@ Control Language="c#" AutoEventWireup="false"
       Codebehind="ucTabStrip.ascx.cs" Inherits="DHPoc.ucTabStrip"
       TargetSchema="http://schemas.microsoft.com/intellisense/ie5"%>
<p>
<ie:TabStrip id="tsMain" runat="server" AutoPostback=true></ie:TabStrip>
</p>
Nothing could be simpler.  On the server side, the control uses XPath to extract the information from the XML file.  One of the required files is the schema (XSD) definition, which is structured as follows:

Using my XSD Editor and XML Editor, it's trivial to define this schema and start entering data into an XML file.

The implementation is again trivial.  Several data tables are used to manage the tab information and style settings:

public class ucTabStrip : System.Web.UI.UserControl
{
protected Microsoft.Web.UI.WebControls.TabStrip tsMain;

private DataTable dtTabs;
private DataTable dtDefaultStyle;
private DataTable dtHoverStyle;
private DataTable dtSelectedStyle;
private DataTable dtSeparatorStyle;
private DataTable dtDim;
...

The dynamic content is created during the Page_Load process for non-postback page loads:

private void Page_Load(object sender, System.EventArgs e)
{
  if (!IsPostBack)
  {
  CreateDynamicContent();
  }
}

Two functions are used to load the tables:

  • GetTabInfo
void GetTabInfo()
{
  string tsName=this.Attributes["TabStrip"];
  string configFilePath=
    System.Configuration.ConfigurationSettings.AppSettings["configFilePath"];

  dtTabs=Xml.GetTable(
  configFilePath+"UCTabStrip.xsd",
  configFilePath+"UCTabStrip.xml",
  "//TabStrip[Name=\""+tsName+"\"]/Tab/*", "*");
}

and

  • GetStyleInfo
void GetStyleInfo()
{
  string tsName=this.Attributes["TabStrip"];
  string configFilePath=
    System.Configuration.ConfigurationSettings.AppSettings["configFilePath"];

  dtDefaultStyle=Xml.GetTable(
   configFilePath+"UCTabStrip.xsd",
   configFilePath+"UCTabStrip.xml",
   "//TabStrip[Name=\""+tsName+"\"]/DefaultStyle/*", "*");

  dtHoverStyle=Xml.GetTable(
    configFilePath+"UCTabStrip.xsd",
    configFilePath+"UCTabStrip.xml",
    "//TabStrip[Name=\""+tsName+"\"]/HoverStyle/*", "*");

  dtSelectedStyle=Xml.GetTable(
    configFilePath+"UCTabStrip.xsd",
    configFilePath+"UCTabStrip.xml",
    "//TabStrip[Name=\""+tsName+"\"]/SelectedStyle/*", "*");

  dtSeparatorStyle=Xml.GetTable(
    configFilePath+"UCTabStrip.xsd",
    configFilePath+"UCTabStrip.xml",
    "//TabStrip[Name=\""+tsName+"\"]/SeparatorStyle/*", "*");

  dtDim=Xml.GetTable(
    configFilePath+"UCTabStrip.xsd",
    configFilePath+"UCTabStrip.xml",
    "//TabStrip[Name=\""+tsName+"\"]/TabWidth | "+
       "//TabStrip[Name=\""+tsName+"\"]/Height", "*");
}

Yes, these could be optimized a bit so that we're not constantly reading in the XSD file for every query to the XML data.

What is this Xml.GetTable thing?  It's a static function which is part of a helper library that I use in many projects to return an XPath query into a nicely formatted DataTable.  It's a bit outside of the scope of this article, so you can look at the code to learn more about how it works.

Notice that the web.config file is used to specify the path for the XSD and XML files, an example of which would appear as follows:

<appSettings>
...
<add key="configFilePath" value="c:\projects.net\defectHunter\" />
...
</appSettings>

Also note the use of the attribute "TabStrip".  I'll discuss this in the section on using the tab strip control.

Finally, the dynamic content is created:

private void CreateDynamicContent()
{
  GetTabInfo();
  GetStyleInfo();
  tsMain.TabDefaultStyle.Add("text-align", "center");
  tsMain.TabDefaultStyle.Add("border-style", "solid");
  tsMain.TabDefaultStyle.Add("color", 
    dtDefaultStyle.Rows[0]["TextColor"].ToString());
  tsMain.TabDefaultStyle.Add("background-color",
    dtDefaultStyle.Rows[0]["BackColor"].ToString());
  tsMain.TabDefaultStyle.Add("border-color",
    dtDefaultStyle.Rows[0]["BorderColor"].ToString());
  tsMain.TabDefaultStyle.Add("border-width",
    dtDefaultStyle.Rows[0]["BorderWidth"].ToString());
  tsMain.TabDefaultStyle.Add("width", dtDim.Rows[0]["TabWidth"].ToString());
  tsMain.TabDefaultStyle.Add("height", dtDim.Rows[0]["Height"].ToString());

  tsMain.TabHoverStyle.Add("color", 
    dtHoverStyle.Rows[0]["TextColor"].ToString());
  tsMain.TabHoverStyle.Add("background-color", 
    dtHoverStyle.Rows[0]["BackColor"].ToString());
  tsMain.TabHoverStyle.Add("border-color", 
    dtHoverStyle.Rows[0]["BorderColor"].ToString());
  tsMain.TabHoverStyle.Add("border-width", 
    dtHoverStyle.Rows[0]["BorderWidth"].ToString());

  tsMain.TabSelectedStyle.Add("border-bottom", "none");
  tsMain.TabSelectedStyle.Add("color", 
    dtSelectedStyle.Rows[0]["TextColor"].ToString());
  tsMain.TabSelectedStyle.Add("background-color", 
    dtSelectedStyle.Rows[0]["BackColor"].ToString());
  tsMain.TabSelectedStyle.Add("border-color", 
    dtSelectedStyle.Rows[0]["BorderColor"].ToString());
  tsMain.TabSelectedStyle.Add("border-width", 
    dtSelectedStyle.Rows[0]["BorderWidth"].ToString());

  tsMain.SepDefaultStyle.Add("border-style", "solid");
  tsMain.SepDefaultStyle.Add("border-top", "none");
  tsMain.SepDefaultStyle.Add("border-left", "none");
  tsMain.SepDefaultStyle.Add("border-right", "none");
  tsMain.SepDefaultStyle.Add("color", 
    dtSeparatorStyle.Rows[0]["TextColor"].ToString());
  tsMain.SepDefaultStyle.Add("background-color", 
    dtSeparatorStyle.Rows[0]["BackColor"].ToString());
  tsMain.SepDefaultStyle.Add("border-color", 
    dtSeparatorStyle.Rows[0]["BorderColor"].ToString());
  tsMain.SepDefaultStyle.Add("border-width", 
    dtSeparatorStyle.Rows[0]["BorderWidth"].ToString());

  foreach (DataRow dr in dtTabs.Rows)
  {
    string tabType=dr["TabStyle"] as string;
    string text=dr["Text"] as string;

    switch(tabType)
    {
      case "Tab":
      {
        Microsoft.Web.UI.WebControls.Tab tab=
          new Microsoft.Web.UI.WebControls.Tab();
        tab.Text=text;
        tsMain.Items.Add(tab);
        break;
      }

      case "Separator":
      {
        Microsoft.Web.UI.WebControls.TabSeparator sep=
          new Microsoft.Web.UI.WebControls.TabSeparator();
        tsMain.Items.Add(sep);
        break;
      }
    }
  }
}

No, there's no try-catch blocks or any other kind of error detection.  ASP.NET does a great job in telling you how you screwed up your XML definition, so I really don't see the need for this.  Once the XML definition is correct, it'll stay correct until you change it.  Note that I also don't implement all the attributes that the tab strip control supports.  I didn't want to go crazy with specifying each attribute when in reality I only really need the color ones.  The rest are pre-set by the user control, so I get a consistent look and feel for all my ASP.NET apps.  If you want additional attributes, it's very trivial as to how the elements in the XSD are created and parsed by the C# code.

Responding To Tab Selection Events

Responding to the tab selection events is actually a bit complicated because the tab strip returns an index of the tab that was selected, while the XML file enumerates a collection of tab objects, including tab separators.  Therefore, to figure out which tab was selected, only the tab components are counted, as the following code illustrates:

private void SelectedIndexChange(object sender, EventArgs e)
{
  GetTabInfo();
  string response="";
  int n=0;
  foreach (DataRow dr in dtTabs.Rows)
  {
    if (dr["TabStyle"].ToString() != "Tab")
    {
      continue;
    }
    if (tsMain.SelectedIndex==n)
    {
      response=dtTabs.Columns.Contains("Response") ? 
        (string)dr["Response"] : "";
      break;
    }
    ++n;
    }
  Response.Write("<script>"+response+"</script>");
  }
}

Now, this is a "What Marc Needs" solution--responding with a response string.  While flexible (for example, you can call a JavaScript function or execute some inline JavaScript code), it may not be what you need.

Using The Tab Strip User Control

To add a tab strip to your form, simply add two lines.  The first line specifies the user control reference and is typically placed at the top of your aspx file:

<%@ Register TagPrefix="uc1" TagName="ucTabStrip" Src="ucTabStrip.ascx" %>

The second line specifies your tab strip inside your form:

<uc1:ucTabStrip id=UcTabStrip1 runat="server" TabStrip="Main">
</uc1:ucTabStrip>

Notice the attribute "TabStrip".  The value of this attribute is the indexer used to access the specific tab strip attribute information located in the XML file.  In the above line, the XML file has a tab strip name of "Main", which appears in the tab strip definition as seen below:

That's it!  That's all that's necessary to add an XML driven tab strip to your form.

The Download

The download includes the following files:

  • xml.cs - the XML XPath query parser
  • ucTabStrip.aspx - the HTML definition
  • ucTabStrip.aspx.cs - the code behind
  • ucTabStrip.xsd - the XSD file
  • ucTabStrip.xml - a sample XML file

The WebControl extension is not included.  If you aren't using it already, download it from Microsoft and follow the installation instructions (which aren't that easy, actually).

Also, feel free to rename the namespaces to your own preferences.

About The DefectHunter Project

This user control, providing dynamic tab creation from an XML specification working with Microsoft's WebControls tabstrip control, has been developed as part of a larger effort to implement a web based defect tracking application with Anders Molin.  This project originally started as a "Code Project Project" effort, was moved off of CP, and then effectively died from lack of participation.  Anders and I are continuing this project on our own.  The DH is positioned as an Open Source effort, and anyone interested in contributing can contact myself or Anders.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here