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

FireFox-like Tab Control

By , 24 Jun 2008
 

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.

/// <summary>
/// override to draw the close button
/// </summary>
/// <param name="e"></param>
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.

/// <summary>
/// Get the stream of the embedded bitmap image
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
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)

About the Author

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

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionUpdatesmemberkiquenet.com26 Feb '13 - 21:59 
Any updates (or fixes) about TabControl for vs2010 and vs2012?
kiquenet.com

QuestionNot Understanding the code.memberRaudraRudra24 Jan '13 - 20:50 
hey, nice article, I want to use it in my project but I am not able to understand the code, Since I am Beginner in C#.
using(Bitmap bmp = new Bitmap(GetContentFromResource(
                "closeinactive.bmp")))
            {
                e.Graphics.DrawImage(bmp,
                    tabTextArea.X+tabTextArea.Width -16, 5, 13, 13);
            }
looking at this code i understand that we will use the bmp, but where and when.
how it able to show me properly closeinactive.bmp...Confused | :confused:
QuestionNicememberghvnd20 Apr '12 - 22:36 
Seems to be pretty nice.
But after all it's not useable.
If I have a control it has to be designer compatible. Drag and drop into my gui instead of declaring the code each time by myselfe. That requires very much time each time xD.
Again nice work Smile | :) .
QuestionIs something wrong in logic?memberehtesham.dotnet22 Dec '11 - 22:27 
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);
        }
    }
}
 

In the above method what's the use of for loop when you are pointing to e.Graphics, i mean whatever tab index may be in loop but you are setting the one which is being drawn as you are using DrawItemEventArgs reference (e), for instance if i have 10 tab pages, in all 10 iteration we will be using e.Graphics which will point to 1 tab page,Can you please justify and correct me if i am wrong
QuestionHow can i resize the horizental of the Tab ConTrolmembervnZest28 Sep '11 - 16:25 
How can i resize the horizental of the Tab ConTrol ?
I want to resize it: the horizental will auto with the text of title Tab
Can you tell me more, i use MS Visual Studio 2010 and it've just upgraded yours to .Net 4
Thanks you very much Wink | ;)
Questionsetting multirow property problemmemberdohboy6420 Sep '11 - 6:30 
by setting the multirow property to true, it makes only the top row of tabs have the close/menu button.
Was hoping for some pointers to fix it
GeneralMy vote of 2memberkore_sar12 Apr '11 - 1:26 
Tried to use.
GeneralMy vote of 5memberGood_shepherd30 Mar '11 - 20:24 
This is what I need. And with good explanation.
GeneralGreat...here are fixes for the width issue and the string vertical alignmentmemberJohn Nagle16 Feb '11 - 3:39 
The adding spaces to the text hack is a bit ugly, and for some reason isn't working in my VS2010 project.
 
To fix this, I did a few things:
 
In the constructor, I added:
 

this.SizeMode = TabSizeMode.Fixed;
this.ItemSize = new Size(50, this.ItemSize.Height); // Just to give it an initial value

 
In the OnDrawItem function, I added the following lines after the DrawString
 

SizeF S = e.Graphics.MeasureString(str, this.Font);
S.Width += 60;
S.Height = this.ItemSize.Height;
Size S2 = new Size();
S2.Width = (int) S.Width;
S2.Height = (int) S.Height;
 
if ( S2.Width != ItemSize.Width )
this.ItemSize = S2;

 

That fixes that...but also your strings are not vertically centered...so I added this BEFORE the DrawString call:
 

stringFormat.LineAlignment = StringAlignment.Center;

 

Thanks again, just what I needed.
Generalabout MenumemberKommrad Homer2 Aug '10 - 4:13 
contextStripMenu is not a contextStrip Menu. but its a contextMenu. so its asking for cast if u try to implement the visual design your way. just wanted to share.so less people try to find out whats wrong Smile | :) nice day
d'Oh!

GeneralCoolmemberPeramikalaza20 Apr '10 - 13:02 
Thanks! I work on some Web Browser for my school project and this is what I need. Smile | :)
Thanks once again.
GeneralRe: Coolmembervijayaprasen12 Jun '10 - 17:05 
Happy to hear it helps your project!! Thumbs Up | :thumbsup:
Prakash

Generalplz help mememberstorm111115 Apr '10 - 16:03 
hi, i have some problem to change the tab page by click a button. anyone can help me about that?? thanks
GeneralMy vote of 1memberGary PR4 Feb '10 - 8:16 
Too much is left out on how things work, especially setting the control to use the ondraw method. Extremely poor explainations. I had to go to other sites to find out why the above code clips didn't work.
GeneralRe: My vote of 1membervijayaprasen2 Mar '10 - 11:33 
thanks for your feedback
Prakash

GeneralNice workmemberoddessa374 Feb '10 - 4:05 
Thankyou, this just what i needed to add the finishing touches to my WebBrowser.Thumbs Up | :thumbsup:
GeneralRe: Nice workmembervijayaprasen2 Mar '10 - 11:34 
happy to hear that!!
Prakash

QuestionUser Controls zoomedmembermerkatore8 May '09 - 6:47 
Hi, first of all apologize for my english, and congratulations on control created.
 
My problem is that any user control that add to one of the TabPage is displayed expanded when running the program. All distances between the components grow and do not correspond to the design view.
I tried adding my user control to the sample code downloaded, and it's the same. I think that I've not created the problem when integrating the TabControlEx to my project.
 
Any solution?
 
Thank you very much.
AnswerRe: User Controls zoomedmembervijayaprasen12 May '09 - 13:07 
if possible please send me a sample project which describes the issue to my mail id. i really dont understand why you are getting a problem.
thanks
Prakash.S
 
Prakash

QuestionFlickeringmemberAKDM14 Apr '09 - 8:19 
I just downloaded the demo and upgraded to VS2005. I see heavy flickering of tabs when hovering and during paint. Anyone else seen this issue and possible solution?
 
Thanks
QuestionRe: Flickeringmemberdansam10017 Apr '09 - 9:47 
Yes. I have the same issue with mine on VS 2005. There is a noticeable amount of flickering and the draw doesn't work often (though the draw problem almost goes away on a computer with better GPU).
 
how do we fix this?
AnswerRe: FlickeringmemberHerman Vos19 Apr '09 - 18:26 
Heavy flickering usually means that doublebuffering is not set.
 
Add this line to TabCtlEx.InitializeComponent
SetStyle(System.Windows.Forms.ControlStyles.DoubleBuffer, True)
 
I run it in .NET 3.5 VS2008 and it runs fine.
 
Good luck!!
GeneralRe: Flickeringmemberdansam10019 Apr '09 - 18:36 
Thanks for the reply.
 
I have this set as well. The flickering I am talking about is not too heavy but is still noticeable.
The code loops through all the pages and draws them...this is noticeable on a slower computer.
If only there was a way to make each item update its drawing spontaneously without having to draw them all.
 
Thanks for the help though...i will make use of what I have now till I can figure something out...probably build a custom control.
GeneralRe: Flickering [modified]memberHerman Vos20 Apr '09 - 9:00 
Seems odd, since you're basically writing on the backbuffer which is invisible.
 
if the control still flickers you can try adding double buffering to your form.
 
Hope this will help: I can't recreate the problem you have. So I can not test the solution above.
 
Good Luck!!
 
modified on Monday, April 20, 2009 7:38 PM removed force draw in WM_PAINT

GeneralRe: Flickeringmemberdansam10021 Apr '09 - 3:11 
Yes very odd. Tried all the changes you suggested and still to no avail...looks like that one won't go away till i move my UI to WPF. The draw is smoother now but the update or refresh is noticeable. You almost see the form loop through all the pages to draw them each time you switch tabs with this solution. I guess it must be just the slow computer.
 
Thanks very much for your help despite. Looks better than it used to be Smile | :)

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 24 Jun 2008
Article Copyright 2007 by vijayaprasen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid