Click here to Skip to main content
Click here to Skip to main content
Go to top

Creating a web based tab control

, 15 May 2006
Rate this:
Please Sign up or sign in to vote.
Creating a web based tab control, with multible tabs.

Introduction

It was always in my mind, why not provide a simple web tab control, something that developers can extend to their needs and requirements. For sure, there are a lot of third party controls providing rich and advanced tab controls, but I couldn't find much articles done on this subject, so far.

Splitting web form controls is really a necessity when you have a large number of controls inside the form, also it helps organizing forms according to specific categories.

Throughout this article, I will focus more on the control designer, facilitating the developer to provide a similar look and feel as the Windows Forms Tab control. While giving the look and feel at runtime is an obvious requirement, it is just a matter of playing with the HTML table cell colors and some images, which will help in providing the 3D look.

Background

Simply, a tab control consists of two things: tab titles and tab body. In my solution, the tab control is represented as a table, with cells containing the titles and tab bodies, similar to the following table:

Title 1 Title 2 .. Title n
Body of tab 1
Body of tab 2
..
Body of tab n

And, this exactly is how the tab control is represented at run time, with the difference that only the active tab body will be visible, whereas all other tabs will be hidden.

Deign and runtime view of our tab control

Basically, the tab control will consist of a tab page collection, MyTabPageCollection, which is a collection of MyTabPage controls. This control contains two main properties, which are the tab title, Title, and the tab body, TabBody of type ITemplate. The System.Web.UI.ITemplate will allow designing and holding other controls.

public class MyTabPage : System.Web.UI.WebControls.PlaceHolder
{
    private string _title = "";
    private ITemplate _tabBody;

    public string Title
    {
        get { return _title;  }
        set { _title = value; }
    }

    [
        PersistenceMode(PersistenceMode.InnerProperty),
        DefaultValue(null),
        Browsable(false)
    ]
    public virtual ITemplate TabBody
    {
        get { return _tabBody;  }
        set { _tabBody = value; }
    }
}

The tab control MyTabControl is a sub class of CompositeControl, which holds all the tabs, and contains some properties to hold the active tab at design time through the CurrentDesignTab property, and at run time through the SelectedTab property. These properties are used to hold the index of the active tab. The tab control designer, MyTabControlDesigner, will switch between the tab templates and activate them based on this index. Multi-tabs are kept in the TabPages collection of type MyTabPageCollection; this collection will allow defining many tabs. The MyTabControl implements two major methods: OnPreRender and CreateChildControls.

protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);
    if (DesignMode)
    {
        _tabPages[_currentDesignTab].TabBody.InstantiateIn(this);
    }
}

protected override void CreateChildControls()
{
    // Always start with a clean form
    Controls.Clear();

    // Create a table using the control's declarative properties
    Table tabControlTable = new Table();
    tabControlTable.CellSpacing = 1;
    tabControlTable.CellPadding = 0;
    tabControlTable.BorderStyle = BorderStyle;
    tabControlTable.Width = this.Width;
    tabControlTable.Height = this.Height;
    tabControlTable.BackColor = ColorTranslator.FromHtml("inactiveborder");

    //keep the selected tab index in a an attribute
    tabControlTable.Attributes.Add("ActiveTabIdx", _selectedTab.ToString());

    BuildTitles(tabControlTable);

    BuildContentRows(tabControlTable);

    // Add the finished tabControlTable to the Controls collection
    Controls.Add(tabControlTable);
}

At design time, the OnPreRender will instantiate the active tab represented by the template, with an index _currentDesignTab in the tab control, whereas the CreateChildControls method is responsible for drawing the control contents, according to the layout described earlier. Also, it will expose the ActiveTabIdx to the attributes of the table which represents the tabs container. The tab titles will be rendered through the BuildTitles method, which iterates through the TabPages collection and draws the titles as table cells. Also, it will create the OnClick event handler call to the JavaScript function ShowTab, which is responsible for showing the active tab.

Our tab control is represented by the table which holds the titles and tab contents. After rendering the titles, we need to render the tab contents. Well, the BuildContentRows method is responsible for rendering the contents, where each tab body is represented by a cell in the table. Here, notice that since the first row of the table will hold the titles then, the tab content row indexing will start from 1, so, the active tab is located at the row index, ActiveTabIdx+1.

For design time view, we don’t need to instantiate all tabs, but only the active tab, as you can see in the method BuildContentRows.

private void BuildContentRows(Table tabControlTable)
{
    // Create content row(s)

    if (DesignMode)
    {
        TableRow contentRow = new TableRow();
        TableCell contentCell = BuildContentCell(contentRow);
        _tabPages[_currentDesignTab].TabBody.InstantiateIn(contentCell);
        tabControlTable.Rows.Add(contentRow);
    }
    else
    {
        int counter = 0;
        foreach (MyTabPage tabPage in _tabPages)
        {
            TableRow contentRow = new TableRow();
            TableCell contentCell = BuildContentCell(contentRow);
            if (tabPage.TabBody != null)
            {
                tabPage.TabBody.InstantiateIn(contentCell);
            }

            //only the selected tab body should be visible
            if (_selectedTab == counter)
            {
                contentRow.Style["display"] = "block";
            }
            else
            {
                contentRow.Style["display"] = "none";
            }
            contentRow.Cells.Add(contentCell);
            tabControlTable.Rows.Add(contentRow);

            counter++;
        }
    }
}

For runtime view, the procedure will iterate throw all the tabs and instantiate the tab bodies in the content rows.

Switching between tabs at the client side

When the user clicks on a tab title, a client-side event handler, ShowTab, will switch the tabs knowing the clicked tab and its current index; the inline comments provide a detailed description of how this procedure works.

<script type="text/javascript" language="javascript">
function ShowTab(tabTitleCell, idx)
{    
    //get the table which holds the tabs
    var tabsTable = tabTitleCell.parentElement.parentElement.parentElement;
    
    //what is the active tab index
    var activeTabIdx = Number(tabsTable.getAttribute("ActiveTabIdx"));
    
    //give the inactive appearance to the previous active tab
    tabsTable.rows[0].cells[activeTabIdx].style["backgroundColor"] = "inactiveborder";
    tabsTable.rows[0].cells[idx].style["backgroundColor"] = "darkgray";
    
    //since the tabs body contained in rows with 
    //index as the same of the tab title link plus 1,
    //then we can hide the row that holds the active tab.
    tabsTable.rows[activeTabIdx + 1].style.display = "none";
    
    //show the active tab body
    tabsTable.rows[idx + 1].style.display = "";
    
    //keep the new active tab in the attribute ActiveTabIdx
    tabsTable.setAttribute("ActiveTabIdx", idx);
}
<script>

Managing tab control templates and switching the design view

The tab control designer should take care of:

  1. Declaring the design area.
  2. Switching between tabs.
  3. Persisting tab bodies, and viewing them in the designer.

The CompositeControlDesigner provides the basic designer, allowing to control the editable regions and creating the child controls. The MyTabControlDesigner inherits the CompositeControlDesigner, to support the tab control design view. The designer contains a private reference, tabControl, to the tab control. This reference gets initialized in the Initialize method. This reference could be used through the designer logic to refer to the tab control.

Declaring the design area

At the time of getting the design time HTML, the overridden method GetDesignTimeHtml will add design regions for all header cells; the region name is prefixed with HEADER_PREFIX, and extended with the tab page index. This way, we can extract the tab index from the region name. Then, a design region is created with similar naming formats as the titles prefixed by CONTENT_PREFIX and the index of the current active tab.

public override String GetDesignTimeHtml(DesignerRegionCollection regions)
{
    int i = 0;

    foreach (MyTabPage tabPage in tabControl.TabPages)
    {
        regions.Add(new DesignerRegion(this, 
                    HEADER_PREFIX + i.ToString()));
        i++;
    }

    EditableDesignerRegion editableRegion =
        new EditableDesignerRegion(this,
            CONTENT_PREFIX + 
            tabControl.CurrentDesignTab, false);
    regions.Add(editableRegion);

    regions[tabControl.CurrentDesignTab].Highlight = true;

    return base.GetDesignTimeHtml();
}

All tab title cells will be marked with the designer region attribute holding the index of the tabs. Also, the content cell will be marked in the design region.

protected override void CreateChildControls()
{
    base.CreateChildControls();

    Table table = (Table) tabControl.Controls[0];

    if (table != null)
    {
        for (int i = 0; i < tabControl.TabPages.Count; i++)
        {
            table.Rows[0].Cells[i].Attributes[
               DesignerRegion.DesignerRegionAttributeName] = 
               i.ToString();
        }

        table.Rows[1].Cells[0].Attributes[
           DesignerRegion.DesignerRegionAttributeName] = 
           (tabControl.TabPages.Count).ToString();
    }
}

Switching between tabs

When the user clicks on the control, OnClick will catch the event. Here, only if the clicked area is a known region, the code will extract the tab index from the region name of the clicked title and set it to the tab control property CurrentDesignTab. Then, the UpdateDesignTimeHtml will update the design view accordingly.

protected override void OnClick(DesignerRegionMouseEventArgs e)
{
    if (e.Region == null)
        return;

    if (e.Region.Name.IndexOf(HEADER_PREFIX) != 0)
        return;
    if (e.Region.Name.Substring(HEADER_PREFIX.Length) != 
        tabControl.CurrentDesignTab.ToString())
    {
        tabControl.CurrentDesignTab = 
          int.Parse(e.Region.Name.Substring(HEADER_PREFIX.Length));

        base.UpdateDesignTimeHtml();
    }
}

Persist tab body, and view it in the designer

While switching between tabs, the designer needs to get the active template which represents the active tab. From the active region name, we can get the tab index, and through getting the active tab from tabControl.TabPages[tabIndex].TabBody and returning the HTML through the ControlPersister.PersistTemplate method. See the method GetEditableDesignerRegionContent.

public override string GetEditableDesignerRegionContent(EditableDesignerRegion region)
{
    IDesignerHost host = (IDesignerHost)
       Component.Site.GetService(typeof(IDesignerHost));

    if (host != null && tabControl.TabPages.Count > 0)
    {
        ITemplate template = tabControl.TabPages[0].TabBody;

        if (region.Name.StartsWith(CONTENT_PREFIX))
        {
            int tabIndex = int.Parse(region.Name.Substring(
                               CONTENT_PREFIX.Length));

            template = tabControl.TabPages[tabIndex].TabBody;
        }

        if (template != null)
            return ControlPersister.PersistTemplate(template, host);
    }

    return String.Empty;
}

Any changes on the template should be reflected to the TabBody of the edited tab. The method ControlParser.ParseTemplate will instantiate a template from the design contents, and by knowing the region name, we can get the tab index and then update the TabBody with the content template. See the method SetEditableDesignerRegionContent.

public override void SetEditableDesignerRegionContent(
       EditableDesignerRegion region, string content)
{
    if (content == null)
        return;

    IDesignerHost host = (IDesignerHost)
       Component.Site.GetService(typeof(IDesignerHost));
    if (host != null)
    {
        ITemplate template = ControlParser.ParseTemplate(host, content);

        if (template != null)
        {
            if (region.Name.StartsWith(CONTENT_PREFIX))
            {
                int tabIndex = int.Parse(
                    region.Name.Substring(CONTENT_PREFIX.Length));
                tabControl.TabPages[tabIndex].TabBody = template;
            }
        }
    }
}

Finally

I didn't do much on coding this control, but I think I provided the basics of creating a web based tab control. I hope this article will be useful for you. Also, I will leave it for your creativity to improve the logic of rendering the control and switching between tabs. It would be good to develop a script object to manage the tab control at the client side, providing properties and the tabs collection, with some methods to automate the process of switching, showing, disabling, and enabling tabs.

In case you have other ideas, or ways to improve this code, please feel free to use this code and update it the way you like. I will appreciate it so much if you will update me with your enhancements.

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

Mohammed Mahmoud Tahtamouni
Web Developer
United Arab Emirates United Arab Emirates
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberFabrizio198613-Jun-12 9:15 
GeneralControls are not Visible Pinmemberayyappa490216-Nov-08 1:24 
GeneralDynamic creation of Tabs Pinmemberayyappa490216-Nov-08 1:22 
QuestionKeep track of the Selected tab between posts. PinmemberAndrader18-Jul-07 13:30 
GeneralVertical Pages Pinmemberanura_b14-Nov-06 20:30 
QuestionHow can we add anothe tab dynamically Pinmemberyaanyere24-Sep-06 22:06 
AnswerRe: How can we add anothe tab dynamically PinmemberMohammed Mahmoud Tahtamouni24-Sep-06 22:59 
QuestionError Rendering Control PinmemberSultan Khan13-Sep-06 21:54 
AnswerRe: Error Rendering Control PinmemberMohammed Mahmoud Tahtamouni21-Sep-06 10:45 
GeneralStrange Behaviour PinmemberTanjuOzgur13-Sep-06 21:06 
GeneralRe: Strange Behaviour PinmemberMohammed Mahmoud Tahtamouni21-Sep-06 10:40 
QuestionError Rendering Control PinmemberNizam Farid Ahmed13-Sep-06 3:02 
AnswerRe: Error Rendering Control PinmemberMohammed Mahmoud Tahtamouni21-Sep-06 10:35 
QuestionCreating Tabs Dynamically? Pinmembersureyya uslu16-Aug-06 4:20 
AnswerRe: Creating Tabs Dynamically? PinmemberMohammed Mahmoud Tahtamouni21-Sep-06 10:48 
GeneralQuestion : web based tab control Pinmemberddieu12-Jul-06 5:24 
GeneralRe: Question : web based tab control PinmemberMohammed Mahmoud Tahtamouni13-Jul-06 6:54 
GeneralCustom Templated CompositeControl/CompositeControlDesigner PinmemberAndrew Clancy5-Jul-06 7:14 
GeneralUse infide FormView ,ASP 2.0 Pinmemberalir27-Jun-06 4:50 
GeneralError Rendering Control PinmemberJRBlack1026-Jun-06 4:18 
GeneralRe: Error Rendering Control PinmemberMohammed Mahmoud Tahtamouni26-Jun-06 4:33 
GeneralRe: Error Rendering Control PinmemberJRBlack1026-Jun-06 5:15 
GeneralRe: Error Rendering Control PinmemberMohammed Mahmoud Tahtamouni27-Jun-06 20:31 
QuestionBrowser Specific? PinmemberJRBlack1020-Jun-06 9:05 
AnswerRe: Browser Specific? PinmemberJRBlack1026-Jun-06 4:18 

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 | Mobile
Web04 | 2.8.140922.1 | Last Updated 15 May 2006
Article Copyright 2006 by Mohammed Mahmoud Tahtamouni
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid