This is a piece of code to deal with tabbed browing, it uses a CompositeControl to deal with creating the tabs and the views. The tabs are created using a simple table setup with the different td elements using different css classes to change the way each of the elements looks.

The class is easily customisable, using a template for both the selected tab and the unselected tabs. The ViewNamed is setup with the name and the description, the name is the name to use for the tab and the description is used to fill in the tooltip for the button.

The following is the code for TabbedPanel.

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.ComponentModel;
using System.Collections.Generic;

namespace WebControls
{
    /// <summary>
    /// Creates a tabbed panel, using a multiview and a table for the menu
    /// at the top. The menu control was not flexible enough to support the
    /// items I wanted supported.
    /// </summary>
    [DefaultProperty("Views")]
    [ToolboxData("<{0}:TabbedMultiview runat=server></{0}:TabbedMultiview>")]
    [ParseChildren(true)]
    public class TabbedPanel : CompositeControl
    {
        private MultiView _multiview;
        private List<ViewNamed> _views;
        private ITemplate _template;
        private ITemplate _selectedTemplate;
        private bool _isLoaded;

        public TabbedPanel() : base()
        {
            _views = new List<ViewNamed>();
            _multiview = new MultiView();
            _multiview.Load += new EventHandler(_multiview_Load);
        }

        private void _multiview_Load(object sender, EventArgs e)
        {
            _isLoaded = true;
            RefreshTableCell(_views[_multiview.ActiveViewIndex]);
        }

        #region DefaultTabMenu
        /// <summary>
        /// Internal class which is the default menu.
        /// </summary>
        public class DefaultTabMenu : ITemplate
        {
            #region ITemplate Members
            public void InstantiateIn(Control owner)
            {
                Literal space = new Literal();
                LinkButton tab = new LinkButton();
                tab.DataBinding += new EventHandler(tab_DataBinding);
                owner.Controls.Add(tab);
            }

            void tab_DataBinding(object sender, EventArgs e)
            {
                LinkButton but = (LinkButton)sender;
                TabMenuContainer cont = (TabMenuContainer)but.NamingContainer;
                but.Text = cont.Name + " ";
                but.CommandName = "Tab";
                but.CommandArgument = cont.ViewId;
                but.ToolTip = cont.Description;
            }
            #endregion

        }
        #endregion

        /// <summary>
        /// The views associated with the panel. These are all ViewNamed
        /// elements.
        /// </summary>
        [Bindable(false)]
        [Category("Data")]
        [Localizable(true)]
        public List<ViewNamed> Views
        {
            get { return _views; }
        }

        /// <summary>
        /// The Css class to use on the <td></td> for the selected menu item.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [Localizable(true)]
        [DefaultValue("")]
        public string SelectedCssClass
        {
            get
            {
                String s = (String)ViewState["SelectedCssClass"];
                return ((s == null) ? String.Empty : s);
            }
            set
            {
                ViewState["SelectedCssClass"] = value;
            }
        }

        /// <summary>
        /// The css class to use on the main section.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [Localizable(true)]
        [DefaultValue("")]
        public string MainSectionCssClass
        {
            get
            {
                String s = (String)ViewState["MainSectionCssClass"];
                return ((s == null) ? String.Empty : s);
            }
            set
            {
                ViewState["MainSectionCssClass"] = value;
            }
        }

        /// <summary>
        /// The css class to use on the menu.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [Localizable(true)]
        [DefaultValue("")]
        public string MenuCssClass
        {
            get
            {
                String s = (String)ViewState["MenuCssClass"];
                return ((s == null) ? String.Empty : s);
            }
            set
            {
                ViewState["MenuCssClass"] = value;
            }
        }

        /// <summary>
        /// The css class to use on unselected items.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [Localizable(true)]
        [DefaultValue("")]
        public string UnselectedCssClass
        {
            get
            {
                String s = (String)ViewState["UnselectedCssClass"];
                return ((s == null) ? String.Empty : s);
            }
            set
            {
                ViewState["UnselectedCssClass"] = value;
            }
        }

        /// <summary>
        /// The active view index, mirrored from inside the multiview.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [Localizable(true)]
        [DefaultValue(0)]
        public int ActiveViewIndex
        {
            get
            {
                return _multiview.ActiveViewIndex;
            }
            set
            {
                int s = _multiview.ActiveViewIndex;
                _multiview.ActiveViewIndex = value;
                if (_isLoaded)
                {
                    if (s != -1)
                    {
                        RefreshTableCell(_views[s]);
                    }
                    RefreshTableCell(_views[value]);
                }
            }
        }

        /// <summary>
        /// The template to use for the tab.
        /// </summary>
        [
        Browsable(false),
        PersistenceMode(PersistenceMode.InnerProperty),
        DefaultValue(typeof(ITemplate), ""),
        Description("Control template"),
        TemplateContainer(typeof(TabMenuContainer))
        ]
        public virtual ITemplate TabTemplate
        {
            get
            {
                return _template;
            }
            set
            {
                _template = value;
            }
        }

        /// <summary>
        /// The template to use for the selected item.
        /// </summary>
        [
        Browsable(false),
        PersistenceMode(PersistenceMode.InnerProperty),
        DefaultValue(typeof(ITemplate), ""),
        Description("Control template"),
        TemplateContainer(typeof(TabMenuContainer))
        ]
        public virtual ITemplate SelectedTabTemplate
        {
            get
            {
                return _selectedTemplate;
            }
            set
            {
                _selectedTemplate = value;
            }
        }

        private void RefreshTableCell(ViewNamed view)
        {
            view.TableCell.Controls.Clear();
            if (view == _multiview.GetActiveView() && _isLoaded)
            {
                view.TableCell.CssClass = SelectedCssClass;
            }
            else
            {
                view.TableCell.CssClass = UnselectedCssClass;
            }
            CreateTabMenu(view, view.TableCell, view.Counter);
        }

        /// <summary>
        /// Sets the current tab to the specified id.
        /// </summary>
        /// <param name="id"></param>
        public void SetCurrentTab(string id)
        {
            ViewNamed currentActiveView = (ViewNamed)_multiview.GetActiveView();
            foreach (ViewNamed view in _views)
            {
                if (view.ID == id)
                {
                    if (view != currentActiveView)
                    {
                        _multiview.SetActiveView(view);
                        RefreshTableCell(view);
                        view.DataBind();
                    }
                }
            }
            RefreshTableCell(currentActiveView);
        }

        private void CreateTabMenu(ViewNamed view, TableCell cell, int counter)
        {
            ITemplate temp;

            view.Counter = counter;

            if (ActiveViewIndex == counter && _isLoaded)
            {
                temp = _selectedTemplate;
            }
            else
            {
                temp = _template;
            }

            if (temp == null)
            {
                temp = new DefaultTabMenu();
            }

            TabMenuContainer cont = new TabMenuContainer();
            cont.ID = "cont" + counter;
            cont.Name = view.Name;
            cont.Description = view.Description;
            cont.ViewId = view.ID;

            temp.InstantiateIn(cont);
            cell.Controls.Add(cont);
            SetupOnCommand(cont);
            cont.DataBind();
        }

        private void SetupOnCommand(Control cont)
        {
            foreach (Control bing in cont.Controls)
            {
                if (bing is Button)
                {
                    Button but = (Button)bing;

                    but.Command += new CommandEventHandler(tab_Command);
                }
                if (bing is LinkButton)
                {
                    LinkButton but = (LinkButton)bing;

                    but.Command += new CommandEventHandler(tab_Command);
                }
                if (bing.Controls.Count > 0)
                {
                    SetupOnCommand(bing);
                }
            }
        }

        protected void tab_Command(object sender, CommandEventArgs e)
        {
            switch (e.CommandName)
            {
                case "Tab":
                    LinkButton but = (LinkButton)sender;
                    TabMenuContainer cont = (TabMenuContainer)but.NamingContainer;
                    TabbedPanel mult = (TabbedPanel)cont.NamingContainer;

                    mult.SetCurrentTab((string)e.CommandArgument);
                    break;
            }
        }

        /// <summary>
        /// Creates all the nessary child controls.
        /// </summary>
        protected override void CreateChildControls()
        {
            Controls.Clear();
            _multiview.ID = "multTabs";

            Table table = new Table();
            table.CellPadding = 0;
            table.CellSpacing = 0;
            table.BorderWidth = 0;
            table.CssClass = MenuCssClass;
            TableRow row = new TableRow();
            table.Rows.Add(row);
            int counter = 0;
            foreach (ViewNamed view in _views)
            {
                TableCell cell = new TableCell();

                _multiview.Views.Add(view);
                view.TableCell = cell;
                cell.CssClass = UnselectedCssClass;
                CreateTabMenu(view, cell, counter);
                row.Cells.Add(cell);
                counter++;
            }
            Controls.Add(table);
            if (MainSectionCssClass != String.Empty)
            {
                Panel pan = new Panel();
                pan.CssClass = this.MainSectionCssClass;
                pan.Controls.Add(_multiview);
                Controls.Add(pan);
            }
            else
            {
                Controls.Add(_multiview);
            }
            base.CreateChildControls();
        }

        /// <summary>
        /// Deal with databinding.
        /// </summary>
        public override void DataBind()
        {
            CreateChildControls();
            ChildControlsCreated = true;
            base.DataBind();
        }

        protected override void OnDataBinding(EventArgs e)
        {
            EnsureChildControls();
            base.OnDataBinding(e);
        }
    }
}

The following is the code for the ViewNamed.

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Web.UI;

namespace WebControls
{
    /// <summary>
    /// View with a text name on it, so we can pull the details out for the tabed browsing stuff.
    /// </summary>
    [DefaultProperty("Name")]
    [ToolboxData("<{0}:NamedView runat=server></{0}:NamedView>")]
    public class ViewNamed : View
    {
        private TableCell _cell;
        private int _counter;

        /// <summary>
        /// Name to display on the tab.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Name
        {
            get
            {
                String s = (String)ViewState["Name"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["Name"] = value;
            }
        }

        /// <summary>
        /// Tool tip to use on the tab.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Description
        {
            get
            {
                String s = (String)ViewState["Description"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["Description"] = value;
            }
        }

        internal TableCell TableCell
        {
            get { return _cell; }
            set { _cell = value; }
        }

        internal int Counter
        {
            get { return _counter; }
            set { _counter = value; }
        }
    }
}

The TabMenuControl class.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebControls
{
    [DefaultProperty("Text")]
    [ToolboxItem(false)]
    public class TabMenuContainer : WebControl, INamingContainer
    {
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Name
        {
            get
            {
                String s = (String)ViewState["Name"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["Name"] = value;
            }
        }

        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Description
        {
            get
            {
                String s = (String)ViewState["Description"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["Description"] = value;
            }
        }

        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string ViewId
        {
            get
            {
                String s = (String)ViewState["ViewId"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["ViewId"] = value;
            }
        }

        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue(false)]
        [Localizable(true)]
        public bool Selected
        {
            get
            {
                object ob = ViewState["Selected"];
                if (ob == null)
                {
                    return false;
                }
                return (bool)ob;
            }
            set
            {
                ViewState["Selected"] = value;
            }
        }

        protected override void OnDataBinding(EventArgs e)
        {
            EnsureChildControls();
            base.OnDataBinding(e);
        }
    }
}