Click here to Skip to main content
Click here to Skip to main content

Tab control for ASP.NET Applications

, 23 Jan 2007
Rate this:
Please Sign up or sign in to vote.
Creating Tab control in ASP.NET

Introduction

This Tab Control is designed for ASP.NET applications. Although you can find many tab controls for ASP.NET from different 3rd parties but the beauty of this control is that its source code reveals internal working of ASP.NET server controls. Once you understand these deep internals, you can write your own professional controls just like 3rd party controls.

Background

In ASP.NET 1.1 and 2.0 there is no tab control. People use IE web controls or 3rd party controls for tab control functionality. Some people write their own logic for tab control by putting their contents in <DIV> tag and changes it visibility when tab header is click. Even you can use ASP.NET 2.0 MultiView control for designing tab control and switches Views by using its ActiveViewIndex property. But the TabsView control is written from the ground means you can change it’s working according to your specific needs.

Using the code

You can add different tab pages in Tabs property of TabsView control. The code below shows how to add tab pages in the control.

<cc1:TabsView ID="TabsView1" runat="server"  CurrentTabIndex="0" 
     SelectedTabCSSClass="SelectedStyle" 
     UnSelectedTabCSSClass="UnSelectedStyle"  >
        <Tabs>
          <cc1:TabPage runat="server" ID="t1" Text="Karachi">
          <asp:TextBox ID="TextBox1" runat="server">Marko Cash&amp;Carry
          </asp:TextBox>
             <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
             This is Karachi the heart of Pakistan. 
          </cc1:TabPage>
         <cc1:TabPage runat="server" ID="t2" Text="Islamabad">
         <asp:TextBox ID="TextBox15" runat="server">1234 S</asp:TextBox>
           <asp:Button ID="Button2" runat="server" Text="Click Me" />
           This is Islamabad the capital of Pakistan. <br />
         <asp:Label ID="Label1" runat="server" Text="Lux" Font-Bold="True">
         </asp:Label>
         </cc1:TabPage>
         <cc1:TabPage runat="server" ID="t3" Text="Lahore">
           This is Lahore the second largest city of Pakistan.
         </cc1:TabPage>
        </Tabs>      
</cc1:TabsView> 

You can add tab pages using SmartTags in VS2005 IDE. Simply right click on the control and click show smart tags.

Known Issues

  • When you add new tab page from the smart tag, you have to assign ID property of new tab page on your own.
  • Whenever you drag new server control on the designer, a unique id of the control is not guaranteed to be generated. Therefore, you have to assign your unique id to control.

How the Controls Works

TabsView control is a Composite Server control. A composite server control is a server control that contains other server controls which are know as its child controls. Internally TabsView control contains Table and Panel server controls. In ASP.NET, composite server control is represented by CompositeControl class. TabsView control is derived from this class.

public class TabsView : CompositeControl

TabsView class contains Designer attribute which represents the designer which is responsible for providing design time functionality to TabsView control.

Designer(typeof(TabViewDesigner))

We create actual child controls in override CreateChildControls() method. This method is called whenever the post back is performed or when the page is first loaded. Whenever you are developing composite server control, always create your child controls in this method. Important thing here is that all child controls should have unique ID in order to avoid naming conflicts. Thanks to the INamingContainer interface which does all the work required for generating the Unique ID of the controls. CompositeControl class already implement this interface so there is no need to implement this interface in our code.

TabsView control contains two portions:

  • Header: Containing tab pages heading. Each header is represented by an instance of LinkButton server control. Each heading is enclosed in table cell for proper alignment.
  • Contents: Containing content of tab pages. Contents are represented by an instance of TabPage class.

Header Rendering

The TabsView control needs to store the IDs of each tab page header and tab page content. One client side their IDs are stored in arrays. The name of these IDs are Control ID qualified meaning if your TabsView has an ID TabsView1 than will be tabButtons_TabsView1 (ID of headers) and tabContents_TabsView1 (ID of contents).This is necessary as a web page contain more than one instance of TabsView server control.

Header array:

var tabButtons_TabsView1 =  new Array("TabsView1_tp0","TabsView1_tp1",
                                      "TabsView1_tp2");

This is due to courtesy of the CreateTabHeaders helper method. This method accepts a Table parameter, a parent table which the control whole alignment of TabsView controls. The contents of above java script array are stored in a variable arrBtns which is an instance of the StringBuilder class.

StringBuilder arrBtns = new StringBuilder();//contains header js

I used Table tblTabs for controlling the alignment of header.

Table tblTabs = new Table();
tblTabs.CellPadding = tblTabs.CellSpacing = 0;

We make sure that CurrentTabIndex property should be less than total no of tab pages. This is done in the VerifyTabIndex method.

private void VerifyTabIndex()
{
   if (CurrentTabIndex >= Tabs.Count)
      throw new Exception("Invalid Tab Index");
}

This method is called in CreateTabHeaders method in order to make sure the correct tab page index of TabsView control. Than we traverse each tab page which is stored in a Tabs collection property, and add LinkButton in a Table cell. The CommandArgument of LinkButton is initialized with 0 based indexes.

VerifyTabIndex();
foreach (TabPage tp in Tabs)
{
        tc = new TableCell();
        lk = new LinkButton();
        lk.ID = "tp" + i.ToString();
        lk.Text = "&nbsp;" + tp.Text + "&nbsp;";
        lk.CommandArgument = i.ToString();
        //rest of the code ..

In case of AutoPostBack mode we make sure that current tab page is set to focus after post back. This is done as follows

if (AutoPostBack) lk.Click += new EventHandler(lk_Click);

In the lk_Click method I called a java script function SelectTab which set focus to current tab.

void lk_Click(object sender, EventArgs e)
{
 
      LinkButton lk = (LinkButton)sender;
 
      this.CurrentTabIndex = Convert.ToInt32(lk.CommandArgument);
            
 
      //select the current tab.
      this.Page.ClientScript.RegisterStartupScript(this.Page.GetType(),
         "SelectTab_LinkButton", "<script language="'JavaScript'">SelectTab(" 
          + CurrentTabIndex +
          ",'" + this.ClientID + "','" + this.ClientID + "_hf" + "','" +
          UnSelectedTabCSSClass + "','" + SelectedTabCSSClass + "')" + 
          "</script>");
}

In case of no post back we simply call SelectTab javascript method on OnClientClick property of LinkButton. The hidden field instance hf is used to store the current tab index of TabsView control.

lk.OnClientClick = "return OnTabClick(this," + i.ToString() + ",'" +
          this.ClientID + "','" + this.ClientID + "_" + hf.ClientID + "','" +
          UnSelectedTabCSSClass + "','" + SelectedTabCSSClass + "');";

We than add link button into the table cell, wrap the table cell alignment and returns a string representation of arrBtns from the function.

        tc.Controls.Add(lk);
        tr.Cells.Add(tc);
        ++i;
      }      
      tblTabs.Rows.Add(tr);
      
 
      tc = new TableCell();
      tc.Controls.Add(tblTabs);      
      tc.Controls.Add(hf);
      
      tr = new TableRow();
      tr.Cells.Add(tc);
       
      tbl.Rows.Add(tr);
      return arrBtns.ToString();
    }

Contents Rendering

In CreateTabContents method we are accepting the parent table parameter that is responsible for controlling the whole layout of TabsView control. Similar to header rendering this function contains tblContents an instance of Table which controls the layout of contents. In the foreach loop we are adding each tab page into the cell of table and appending the id of each tab page in StringBuilder instance arrTabPages.

foreach (TabPage tp in Tabs)
{
        tpId = this.ClientID + "_" + tp.ID;        
        tc.Controls.Add(tp);
        if (i == tabs.Count - 1)
          arrTabPages.AppendFormat("\"{0}\"", tpId);
        else
          arrTabPages.AppendFormat("\"{0}\",", tpId);
        ++i; 
}

Child Controls Creation

The CreateTabHeaders and CreateTabContents are called from CreateChildControls overriden method.These functions return the string array containing the id’s of header link buttons and tab pages. Using these id’s we register the java script array declaration ClientScript.RegisterArrayDeclaration method.

//create tab headers.
js = CreateTabHeaders(ref tbl);
this.Page.ClientScript.RegisterArrayDeclaration("tabButtons_" 
                                                + this.ClientID, js);
        
//create tab contents.        
js = CreateTabContents(ref tbl);
this.Page.ClientScript.RegisterArrayDeclaration("tabContents_" + 
                                                this.ClientID, js);

Now the tricky part of the code, how the TabsView control know the current index when user moves between different tabs without any post back. This is why we used hidden field instance hf so that we can store the index in the hidden using java script (TabsView.js OnTabClick function)

//store the current index in hidden field.
document.getElementById(hfId).value = i;

We read the value of this hidden field using Request object. You see from the code that I used following if condition before reading the Request object.

if ( !this.DesignMode /*HttpContext.Current != null*/)

This is because in a designer the Request objects is null therefore, reading Request object in this case produces error. We use Control base class DesignMode property for checking this condition. You can also use HttpContext.Current != null here.

Properties.

  • Tabs: Tabs is a collection property. Whenever you use collection property the most appropriate approach is to write your own custom collection class. Although use can also use built in collections like ArrayList,Stack etc. I implemented Tabs as Inner default property. Whenever you use inner property, you have to specify its persistence mode. Without the persistence mode ASP.NET does not know how to persist these properties. You can not see this property in property window through the courtesy of Browsable(false) attribute.
    [
          PersistenceMode(PersistenceMode.InnerDefaultProperty),
          DefaultValue(null) ,
          Browsable(false)
    ]
    public virtual TabPageCollection Tabs
    {
          get
          {
            if (tabs == null)
            {
              tabs = new TabPageCollection(this);
            }
     
            return tabs;
          }
     
    }
    

    Every tab page is inherited from Panel Server control. Therefore, all properties of Panel control are applicable to individual tab page. For example you can create scrollable tab page.

    <cc1:TabPage runat="server" ID="t3" Text="Text" ScrollBars="Auto">
          <!-—your contents-->
    </cc1:TabPage>
  • CurrentTabIndex : Get the current tab index. The set part is fairly simple. In the get part I raise TabSelectionChangingObject event when the new index and the previous index are not same

  • SelectedTabCSSClass and UnSelectedTabCSSClass are simple and they use ViewState to persist their values across postback.

Events

  • § TabSelectionChangingHandler: Whenever you support events in your server control the recommended method is to use add and remove keywords.Every server control maintains a list of delegates.This list contain all events associated with server control.This list is expose through Events property. You use AddHandler and RemoveHandler to attach and remove events to server controls.
public event TabSelectionChangingHandler TabSelectionChanging
    {
      add {  Events.AddHandler(TabSelectionChangingObject, value); }
      remove {  Events.RemoveHandler(TabSelectionChangingObject, value); }
    }

Designer Working

TabsView designer is written using the TabsViewDesigner class.This class is inherited from CompositeControlDesigner which provides the desin time behavior for composite server control.To understand its working considere the following steps.

First of all you need to capture an instance of actual composite server control. This is done in overridden Initialize method.

public override void Initialize(IComponent component)
{
      base.Initialize(component);
      tabView = (TabsView)component;
}

Than you create the child controls of TabsView control in overridden method CreateChildControls.For each part of the control we have to associate an id so that we can know about which region is actually clicked. I associate different ids for TabsView header and single id for each TabsView content region because only single content is active in the designer.

if (tabViewTable.Rows.Count > 0)
      {
        //header table 
        headerTable = (Table)tabViewTable.Rows[0].Cells[0].Controls[0];
 
        //make sure we have headers.
        if (headerTable != null)
        {
          //we are creating designer regions consiting of headers and 
          //contents.
          int i;
 
          //header tabs.
          for (i = 0; i < headerTable.Rows[0].Cells.Count; i++)
            headerTable.Rows[0].Cells[i].Attributes[
                 DesignerRegion.DesignerRegionAttributeName] = i.ToString();
 
          //header contents.Single region for all contents b/c only single 
          //content is active at a time.
          contentsTable = (Table)tabViewTable.Rows[1].Cells[0].Controls[0];
          contentsTable.Rows[0].Cells[0].Attributes[
                 DesignerRegion.DesignerRegionAttributeName] = i.ToString();
        }
      }

Than handle designer onclick event. This is called when you click on the TabsView header region. To make sure that we click on header I check the name of the header.

if (e.Region.Name.IndexOf("Header") == -1)
        return;
 
      if (e.Region != null)
      {
        currentTabIndex = tabView.CurrentTabIndex = 
                        int.Parse(e.Region.Name.Substring("Header".Length));
        base.UpdateDesignTimeHtml();
      }

Than you need to tell about designer region and editable designer region. Our headers are designer region and content tabs are editable designer region. After specifying this I select the current tab with regions[tabView.CurrentTabIndex].Highlight = true;

Table tabViewTable = (Table)tabView.Controls[0];
      int i = 0, designerRegionIndex = 0;
      string designTimeHtml = "";
 
      if (tabViewTable.Rows.Count > 0)
      {
        Table headerTable = (Table)tabViewTable.Rows[0].Cells[0].Controls[0];
        for (i = 0; i < headerTable.Rows[0].Cells.Count; i++)
        {
          regions.Add(new DesignerRegion(this, "Header" +
                                          designerRegionIndex.ToString()));
          ++designerRegionIndex;
        }
 
        Table contentTable = (Table)tabViewTable.Rows[1].Cells[0].Controls[0];
        //for (i = 1; i < /*2*/tabView.Tabs.Count; i++)
        //for (i = 0; i < /*2*/tabView.Tabs.Count; i++)
        {
          EditableDesignerRegion editableRegion =
            new EditableDesignerRegion(this,
                "Content" + tabView.CurrentTabIndex, false);
          ++designerRegionIndex;
          editableRegion.UserData = i;
          regions.Add(editableRegion);
        }
 
 
        //Set the highlight for the selected region
        if (tabView.Tabs.Count > 0)
          regions[tabView.CurrentTabIndex].Highlight = true;
 
        designTimeHtml = base.GetDesignTimeHtml(regions);
      }
      else designTimeHtml = GetDesignTimeHtml();
 
 
      return designTimeHtml;

Provide persistence among individual tabs using overridden GetEditableDesignerRegionContent and SetEditableDesignerRegionContent.I persist the individual tab content with ControlPersister.PersistControl(tb, host).

public override string GetEditableDesignerRegionContent(
                                               EditableDesignerRegion region)
    {
      //Get a reference to the designer host
      IDesignerHost host = (IDesignerHost)Component.Site.GetService(
                                                       typeof(IDesignerHost));
      if (host != null)
      {
        TabPage tb;
        tb = (TabPage)tabView.Tabs[tabView.CurrentTabIndex];          
        return ControlPersister.PersistControl(tb, host);
      }
      return String

For every change in individual tab content we have to update the TabsView control.The SetEditableDesignerRegionContent does the same. It parses the individual tab contents from its string representation and update the TabsView control.

public override void SetEditableDesignerRegionContent(
                               EditableDesignerRegion region, string content)
    {
      if (content == null)
        return;
 
      // Get a reference to the designer host
      IDesignerHost host = 
              (IDesignerHost)Component.Site.GetService(typeof(IDesignerHost));
      if (host != null)
      {
               
        TabPage view = (TabPage)ControlParser.ParseControl(host, content);
        
        if (view != null)
        {
          int tabIndex = int.Parse(region.Name.Substring("Content".Length ));
          tabView.Tabs.RemoveAt(tabIndex);
          tabView.Tabs.AddAt(tabIndex, view);
          
        }
      }
    }

Adding Custom Action List

To add new panel in smart tags we have to override the ActionLists property and need to create an instance of DesignerActionListCollection class.

 public override DesignerActionItemCollection GetSortedActionItems()
      {
        if (items == null)
        {
          // Create the collection
          items = new DesignerActionItemCollection();
                    
          // Add Tab Page command
          items.Add(new DesignerActionMethodItem(this, "CreateTabPage", 
                                                 "Add Tab Page", true)); 
        }
        return items;
      }

Every individual item in this panel is represented by instance of DesignerActionMethodItem class. Individual panel items are associated together to form a panel by a call to the GetSortedActionItems method of the DesignerActionList class.

// Get the sorted list of Action items
      public override DesignerActionItemCollection GetSortedActionItems()
      {
        if (items == null)
        {
          // Create the collection
          items = new DesignerActionItemCollection();
                    
          // Add Tab Page command
          items.Add(new DesignerActionMethodItem(this, "CreateTabPage", 
                                                 "Add Tab Page", true));  
        }
        return items;
      }

Whenever the Add Tab Page command is executed it calls a CreateTabPage method defined in the class. This method in turns add new tab page callback.

public void CreateTabPage()
{
    TabsView tabView = (TabsView)_parent.Component;
    // Create the callback
    TransactedChangeCallback toCall =
                                   new TransactedChangeCallback(AddTabPage);
    // Create the transacted change in the control
    ControlDesigner.InvokeTransactedChange(tabView, toCall, null, 
                                                 "Add Tab Page");
}

Here we need to understand the concept of designer transactions. Whenever, you add new tab page using smart tag it performs and atomic designer transaction which adds new tab page. Whenever, you undo this action it will simply rollback your last action resulting the removal of last added tab page. You can see the Undo option in your Edit menu as show below:

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

Share

About the Author

M.Tahir Ali

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 2 Pinmemberrobzz217-Sep-12 1:18 
GeneralMy vote of 1 Pinmembernaresh@hits26-Sep-11 23:33 
GeneralRe: My vote of 1 PinmemberTahir 1112-Nov-11 14:52 
QuestionThanks a lot! It works PERFECT for me. PinmemberCathy Chia23-Jun-11 20:30 
QuestionInput String was not in a correct format error Pinmemberema0054-Nov-10 0:05 
GeneralMy vote of 1 PinmemberRogic26-Sep-10 23:11 
GeneralSet the tab contents at page load PinmemberSharkjazz24-Apr-09 2:04 
GeneralRe: Set the tab contents at page load PinmemberSharkjazz24-Apr-09 3:32 
BugRe: Set the tab contents at page load Pinmemberjuntaochen17-Jul-12 4:18 
Generalissue with two smartcontrol on same page Pinmembersanketrao11-Feb-09 2:28 
GeneralRe: issue with two smartcontrol on same page Pinmemberpsy-neo6-Apr-09 23:41 
Generaladd new tab on client PinmemberMember 400536018-Dec-07 6:46 
QuestionHow to create nested TabsViews? Pinmemberslaviuss24-Oct-07 22:29 
Questionadd TabsView TabPages in code Pinmemberjohn_j212-Oct-07 5:49 
AnswerRe: add TabsView TabPages in code PinmemberTahir 1113-Oct-07 19:26 
GeneralDrop down is not working in runtime when I use withTab control for ASP.NET Applications PinmemberLeena Mercy24-Aug-07 0:30 
GeneralRe: Drop down is not working in runtime when I use withTab control for ASP.NET Applications Pinmembersandeepkamboj43521-Jun-10 1:46 
QuestionIts Not working in Master page [modified] PinmemberHIuma 0071-Aug-07 20:26 
AnswerRe: Its Not working in Master page PinmemberHIuma 0071-Aug-07 22:08 
Generalasp.net(linkbutton) Pinmemberling_luv25-Jul-07 16:28 
GeneralRe: Even you can use PinmemberTahir 11128-Jun-07 5:16 
GeneralRe: Even you can use Pinmembernethatix11-Aug-07 10:08 
GeneralRe: Even you can use PinmemberStephane Briere11-Aug-07 20:20 
GeneralRe: Even you can use PinmemberTahir 11118-Aug-07 10:11 
GeneralRe: Even you can use Pinmemberexpresso100018-Aug-07 18:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 23 Jan 2007
Article Copyright 2007 by M.Tahir Ali
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid