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

FireFox-like Tab Control

, 24 Jun 2008
Rate this:
Please Sign up or sign in to vote.
An article on Tab Control

Screenshot - image001.png

Introduction

Recently I came across a requirement for a tab control to be closed from the header itself. This means the user doesn't need to switch to any tab page in order to close it. The perfect example for this one is Firefox browser. In Firefox, the user can open as many tabs as he wants and he can close any tab at any time without opening the tab. I tried Googling a solution and didn't find anything that was exactly what I wanted. Then I thought about implementing my own tab control with the same functionality. So finally, here is the control.

Using the Code

The control enables the designer to add/remove tab pages like a normal tab control with the new look and feel. The classes that support this features are:

  1. TabCtlEx.cs (inherited from System.Windows.Forms.TabControl)
  2. TabPage.cs (inherited from System.Windows.Forms.TabPage)

Using this control is straightforward, like the .NET tab control.

private MyControlLibrary.TabCtlEx userControl11;
    TabPageEx tabPage1;
    TabPageEx tabPage2;
    TabPageEx tabPage3;

private TabPageEx tabPage4;
    this.userControl11 = new MyControlLibrary.TabCtlEx();
    this.tabPage1 = new MyControlLibrary.TabPageEx(this.components);
    this.tabPage2 = new MyControlLibrary.TabPageEx(this.components);
    this.tabPage3 = new MyControlLibrary.TabPageEx(this.components);
    this.tabPage4 = new MyControlLibrary.TabPageEx(this.components);
    this.Controls.Add(this. userControl11);

Drawing the Close Button on Each Tab Header

This requires you to override the existing OnDrawItem() function in the .NET tab control.

/// <span class="code-SummaryComment"><summary></span>
/// override to draw the close button
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="e"></param></span>
protected override void OnDrawItem(DrawItemEventArgs e)
{
    RectangleF tabTextArea = RectangleF.Empty;
    for(int nIndex = 0 ; nIndex < this.TabCount ; nIndex++)
    {
        if( nIndex != this.SelectedIndex )
        {
            /*if not active draw ,inactive close button*/
            tabTextArea = (RectangleF)this.GetTabRect(nIndex);
            using(Bitmap bmp = new Bitmap(GetContentFromResource(
                "closeinactive.bmp")))
            {
                e.Graphics.DrawImage(bmp,
                    tabTextArea.X+tabTextArea.Width -16, 5, 13, 13);
            }
        }
        else
        {
            tabTextArea = (RectangleF)this.GetTabRect(nIndex);
            LinearGradientBrush br = new LinearGradientBrush(tabTextArea,
                SystemColors.ControlLightLight,SystemColors.Control,
                LinearGradientMode.Vertical);
            e.Graphics.FillRectangle(br,tabTextArea);

            /*if active draw ,inactive close button*/
            using(Bitmap bmp = new Bitmap(
                GetContentFromResource("close.bmp")))
            {
                e.Graphics.DrawImage(bmp,
                    tabTextArea.X+tabTextArea.Width -16, 5, 13, 13);
            }
            br.Dispose();
        }
        string str = this.TabPages[nIndex].Text;
        StringFormat stringFormat = new StringFormat();f
        stringFormat.Alignment = StringAlignment.Center; 
        using(SolidBrush brush = new SolidBrush(
            this.TabPages[nIndex].ForeColor))
        {
            /*Draw the tab header text
            e.Graphics.DrawString(str,this.Font, brush,
            tabTextArea,stringFormat);
        }
    }
}

Here, the close button is actually the bitmap image drawn over each tab header. So, in just giving the look and feel of a button used in Firefox, three different bitmap images are used:

  • closeinactive.bmp
  • close.bmp
  • onhover.bmp

These images are embedded with the control and extracted using reflection. There is also a function used to get the embedded image resource. This function returns the stream and creates an image by passing the stream to a bitmap class, as described above.

/// <span class="code-SummaryComment"><summary></span>
/// Get the stream of the embedded bitmap image
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="filename"></param></span>
/// <span class="code-SummaryComment"><returns></returns></span>
private Stream GetContentFromResource(string filename)
{
    Assembly asm = Assembly.GetExecutingAssembly();
    Stream stream =asm.GetManifestResourceStream(
        "MyControlLibrary."+filename);
    return stream;
}

Firefox asks the user to confirm whether he wants to close the tab page or not. I like to have the same functionality for my control, since sometimes the user may press the close button accidentally and doesn't want to lose the changes done. This is accomplished by setting the Boolean property.

private bool confirmOnClose = true;
public bool ConfirmOnClose
{
    get
    {
        return this.confirmOnClose;
    }
    set
    {
        this.confirmOnClose = value;
    }
}

Property

ConfirmOnClose confirms with a message box before closing the tab page. Here is the code to check if the clicked area is inside the bitmap rectangle or not.

protected override void OnMouseDown(MouseEventArgs e)
{
    RectangleF tabTextArea = (RectangleF)this.GetTabRect(SelectedIndex);
    tabTextArea = 
        new RectangleF(tabTextArea.X+tabTextArea.Width -16,5,13,13);
    Point pt = new Point(e.X,e.Y);
    if(tabTextArea.Contains(pt))
    {
        if(confirmOnClose)
        {
            if(MessageBox.Show("You are about to close "+
                this.TabPages[SelectedIndex].Text.TrimEnd()+
                " tab. Are you sure you want to continue?","Confirm close",
                MessageBoxButtons.YesNo) == DialogResult.No)
            return;
        }
        //Fire Event to Client
        if(OnClose != null)
        {
            OnClose(this,new CloseEventArgs(SelectedIndex));
        }
    }
}

Whenever the close button on the tab header is pressed, the control will fire an OnClose() event to the client with the clicked tab index as the argument. This will give some more flexibility to the client to do something before closing the tab.

Conclusion

It's just yet another tab control that provides some flexibility and an improved user interface. I found this functionality very useful in some scenarios. Feedbacks are welcome!

History

Updated with menu button functionality. The above description is just for adding the close button, but the article source code contains the actual implementation for both the close button and the menu button. The major advantage with this control is that menu item functionality is applied to each tab page instead of tab control. In other words, we can have different menu items for different tab pages.

If there is no menu attached with any of the tab pages, it won't display the menu button. The below picture shows that tabPage3 doesn't have a menu attached and so doesn't display the Menu button for that tab page only.

Screenshot - image004.jpg

The actual code for attaching the menu item is:

this.tabPage1.Menu
    = contextMenuStrip1;
………
this.tabPage2.Menu
    = contextMenuStrip1;
………
this.tabPage3.Menu
    = contextMenuStrip1;
………
this.tabPage4.Menu
    = contextMenuStrip1;

Either you can assign the same menu item or a different menu item for each tab page.

History

  • 16 August, 2007 -- Original version posted
  • 28 August, 2007 -- Article and download updated
  • 24 June, 2008 -- source updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

vijayaprasen
Software Developer (Senior)
United States United States
worst fellow working as a consultant in a Telecom company (USA)
knows little bit on C,C++ and C# with 5+ years of experience.

Comments and Discussions

 
GeneralRe: set tab size PinmemberMember 23518431-Jul-08 20:32 
GeneralRe: set tab size PinmemberIlíon29-Jul-08 8:59 
GeneralRe: set tab size PinmemberMember 235184329-Jul-08 19:37 
GeneralHCI issue PinmemberDerek Bartram25-Jun-08 1:17 
GeneralRe: HCI issue Pinmembervijayaprasen28-Jun-08 4:57 
GeneralException Pinmembergborges20-May-08 10:11 
GeneralRe: Exception Pinmembervijayaprasen20-May-08 13:34 
GeneralRe: Exception; suggested solution [modified] PinmemberIlíon27-Jun-08 4:26 
First off, I want to say that I think that Vijayaprasen's control/code is pretty good. I certainly appreciate the work he put into it; I don't think I could have come up with it.
 
gborges wrote:
There is an exception on line 211 at TabCtrlEx.cs file
 
Unable to cast object of type 'System.Windows.Forms.TabPage' to type 'MyControlLibrary.TabPageEx'.
 
Using visual c# 2008.

I noticed the same thing, right away Cry | :(( (I'm using VS2003).
 
My effort to correct this error has turned into some extensive modifications to the code.
 
gborges wrote:
Any suggestions in how to solve it?

My solution to the problem was to write a class inheriting from [System.Windows.Forms.TabControl.TabPageCollection] and then in the [TabCtlEx] class over-ride (use the 'new' modifier) the [TabCtlEx.TabPages] property using this new class.
 
Also, it wouldn't hurt to over-ride the [TabCtlEx.Controls] property, but this doesn't seem to be strictly necessary for this particular problem.
 

Here is my suggested solution to this problem:
 

========= Over-ride [TabCtlEx.TabPages] Property ========
private FireFoxTab.FireFoxTabPageCollection _TabPages = null;
[Category ("Misc")]
[Description ("The FireFoxTabPages in the FireFoxTabCtl.")]
public new FireFoxTab.FireFoxTabPageCollection TabPages
{
	get { return this._TabPages; }
}
========= END Over-ride [TabCtlEx.TabPages] Property ========
 
========= new file [FireFoxTabPageCollection.cs] ========
using System;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Windows.Forms;
 
namespace FireFoxTab
{
	/// <summary>
	/// Summary description for FireFoxTabPageCollection.
	/// </summary>
	public class FireFoxTabPageCollection 
		: System.Windows.Forms.TabControl.TabPageCollection
	{
		#region pseudo-[Component Designer generated instantiation of components]
 
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;
 
		#endregion 
 
		#region pseudo-[Component Designer generated code]
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			components = new System.ComponentModel.Container();
		}
		#endregion
 
 
		#region Private Fields and Properties 
		private FireFoxTab.FireFoxTabCtl _owner = null;
		#endregion 
 
		#region Class Constructor (and Dispose)
		// public FireFoxTabPageCollection(FireFoxTab.FireFoxTabCtl owner) 
			: base((System.Windows.Forms.TabControl)owner)
		// 
		// protected void Dispose( bool disposing )
		// 
		public FireFoxTabPageCollection(FireFoxTab.FireFoxTabCtl owner) 
			: base((System.Windows.Forms.TabControl)owner)
		{
			///
			/// Required for Windows.Forms Class Composition Designer support
			///
			InitializeComponent();
 
			//
			// TODO: Add any constructor code after InitializeComponent call
			//
			_owner = owner;
		}
 
 
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		//protected override void Dispose( bool disposing )
		protected void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			//base.Dispose( disposing );
		}
		#endregion 
 
		#region New/Override and Novel Properties
		// public new FireFoxTab.FireFoxTabPage this[int index]
		//
		// the .Item (.TabPage) indexer
		public new FireFoxTab.FireFoxTabPage this[int index]
		{
			get
			{
				int idx = 0;
				if (index > 0)
				{
					idx = index;
				}
				if (idx >= this.Count)
				{
					idx = this.Count - 1;
				}
 
				if (idx < 0)
				{
					return null;
				}
				return (base[idx] as FireFoxTab.FireFoxTabPage);
			}
		}
		#endregion 
 
		#region New/Override and Novel Methods/Functions 
		// public void Add(FireFoxTab.FireFoxTabPage ffTabPage)
		// public void AddRange(FireFoxTab.FireFoxTabPage[] ffTabPages)
		// 
		// public bool Contains(FireFoxTab.FireFoxTabPage ffTabPage)
		// 
		public void Add(FireFoxTab.FireFoxTabPage ffTabPage)
		{
			// Any special processing may be done here
			//
			base.Add(ffTabPage);
		}
 
		public void AddRange(params FireFoxTab.FireFoxTabPage[] ffTabPages)
		{
			//base.AddRange(ffTabPages);
			foreach (FireFoxTab.FireFoxTabPage _ffTabPage in ffTabPages)
			{
				this.Add(_ffTabPage);
			}
		}
 
 
		public bool Contains(FireFoxTab.FireFoxTabPage ffTabPage)
		{
			return base.Contains(ffTabPage);
		}
		#endregion 
	}
}
========= END [FireFoxTabPageCollection.cs] ========
Edit:
If you include that code straight into your project, it won't compile because the name [FireFoxTab.FireFoxTabPage] will be undefined. As I said, I've been modifying the original code; I changed the original names of the namespace and classes.
GeneralRe: Exception; suggested solution Pinmembersth_Weird17-Feb-09 23:18 
GeneralRe: Exception; suggested solution PinmemberIlíon18-Feb-09 0:14 
QuestionOwner project Pinmembereusta12-Apr-08 3:06 
GeneralRe: Owner project Pinmembervijayaprasen12-Apr-08 14:17 
GeneralException PinmemberGnanadurai12-Dec-07 2:09 
GeneralRe: Exception Pinmembervijayaprasen25-Dec-07 11:16 
GeneralRe: Exception PinmemberGnanadurai25-Dec-07 17:03 
GeneralRe: Exception Pinmembervijayaprasen26-Dec-07 9:47 
GeneralSmall correction needed... PinmemberLeon v Wyk29-Nov-07 2:46 
GeneralRe: Small correction needed... Pinmembervijayaprasen30-Nov-07 11:32 
GeneralPerfect Job PinmemberBehzad Sedighzadeh8-Nov-07 1:36 
GeneralRe: Perfect Job Pinmembervijayaprasen9-Nov-07 3:31 
GeneralVS2005 compatibility PinmemberDroopy21-Sep-07 2:47 
GeneralRe: VS2005 compatibility Pinmembervijayaprasen21-Sep-07 3:15 
GeneralRe: VS2005 compatibility Pinmembervijayaprasen21-Sep-07 7:44 
GeneralRe: VS2005 compatibility PinmemberDave - O24-Mar-08 19:10 
GeneralRe: VS2005 compatibility Pinmembervijayaprasen27-Mar-08 11:04 

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
Web01 | 2.8.140814.1 | Last Updated 24 Jun 2008
Article Copyright 2007 by vijayaprasen
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid