TabControl with Close and Add Button

13,915

Solution 1

As an option you can add an extra tab which shows an add icon Add and check when the user clicks on that tab, then insert a new TabPage before it.

Also you can prevent selecting that extra tab simply using Selecting event of TabControl. This way the last tab acts only like an add button for you, like IE and Chrome.

Tab with close and add button

Implementation Details

We will use an owner draw tab to show close icons on each tab an add icon on the last tab. We use DrawItem to draw close and add icons, MouseDown to handle click on close and add buttons, Selecting to prevent selecting of the last tab and HandleCreated to adjust tab width. You can see all implementation settings and codes below.

Initialization

Set padding and DrawMode and assign event handlers for DrawItem, MouseDown, Selecting and HandleCreated event.

this.tabControl1.Padding = new Point(12, 4);
this.tabControl1.DrawMode = TabDrawMode.OwnerDrawFixed;

this.tabControl1.DrawItem += tabControl1_DrawItem;
this.tabControl1.MouseDown += tabControl1_MouseDown;
this.tabControl1.Selecting += tabControl1_Selecting;
this.tabControl1.HandleCreated += tabControl1_HandleCreated;

Handle click on close button and add button

You can handle MouseDown or MouseClick event and check if the last tab rectangle contains the mouse clicked point, then insert a tab before the last tab. Otherwose check if one of close buttons contains clicked location, then close the tab which its close button was clicked:

private void tabControl1_MouseDown(object sender, MouseEventArgs e)
{
    var lastIndex = this.tabControl1.TabCount - 1;
    if (this.tabControl1.GetTabRect(lastIndex).Contains(e.Location))
    {
        this.tabControl1.TabPages.Insert(lastIndex, "New Tab");
        this.tabControl1.SelectedIndex = lastIndex;
    }
    else
    {
        for (var i = 0; i < this.tabControl1.TabPages.Count; i++)
        {
            var tabRect = this.tabControl1.GetTabRect(i);
            tabRect.Inflate(-2, -2);
            var closeImage = Properties.Resources.DeleteButton_Image;
            var imageRect = new Rectangle(
                (tabRect.Right - closeImage.Width),
                tabRect.Top + (tabRect.Height - closeImage.Height) / 2,
                closeImage.Width,
                closeImage.Height);
            if (imageRect.Contains(e.Location))
            {
                this.tabControl1.TabPages.RemoveAt(i);
                break;
            }
        }
    }
}

Prevent selectin last tab

To prevent selection the last tab, you can handle Selecting event of control and check if the selecting tab is the last tab, cancel the event:

private void tabControl1_Selecting(object sender, TabControlCancelEventArgs e)
{
    if (e.TabPageIndex == this.tabControl1.TabCount - 1)
        e.Cancel = true;
}

Draw Close Button and Add Button

To draw close button and add button, you can handle DrawItem event. I used these icons for add Add and close Close buttons.

private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
    var tabPage = this.tabControl1.TabPages[e.Index];
    var tabRect = this.tabControl1.GetTabRect(e.Index);
    tabRect.Inflate(-2, -2);
    if (e.Index == this.tabControl1.TabCount - 1)
    {
        var addImage = Properties.Resources.AddButton_Image;
        e.Graphics.DrawImage(addImage,
            tabRect.Left + (tabRect.Width - addImage.Width) / 2,
            tabRect.Top + (tabRect.Height - addImage.Height) / 2);
    }
    else
    {
        var closeImage = Properties.Resources.DeleteButton_Image;
        e.Graphics.DrawImage(closeImage,
            (tabRect.Right - closeImage.Width),
            tabRect.Top + (tabRect.Height - closeImage.Height) / 2);
        TextRenderer.DrawText(e.Graphics, tabPage.Text, tabPage.Font,
            tabRect, tabPage.ForeColor, TextFormatFlags.Left);
    }
}

Adjust Tab width

To adjust tab width and let the last tab have smaller width, you can hanlde HandleCreated event and send a TCM_SETMINTABWIDTH to the control and specify the minimum size allowed for the tab width:

[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
private const int TCM_SETMINTABWIDTH = 0x1300 + 49;
private void tabControl1_HandleCreated(object sender, EventArgs e)
{
    SendMessage(this.tabControl1.Handle, TCM_SETMINTABWIDTH, IntPtr.Zero, (IntPtr)16);
}

Download

You can download the code or clone the repository here:

Solution 2

Normally, the direct, "low-level" way to do something like this would be to handle the Paint event and draw into the TabControl itself, and then also handle mouse input events to detect clicks where you have drawn.

However, a) that's a pain, and b) the TabControl suppresses the Paint event, so it's not possible to handle without going even lower-level and dealing with the WM_PAINT message in a WndProc() method override.

For your purposes, I would recommend simply adding a new control, e.g. a Button, to the Form, placing it just over the place on the TabControl where you want the user to be able to click. Then in the Button.Click event handler, you can add a new page as desired. If you want to encapsulate the combination of the Button and the TabControl, you can use a UserControl.

For example:

TabControlWithAdd.Designer.cs:

partial class TabControlWithAdd
{
    /// <summary> 
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region 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()
    {
        this.button1 = new System.Windows.Forms.Button();
        this.tabControl1 = new System.Windows.Forms.TabControl();
        this.tabPage1 = new System.Windows.Forms.TabPage();
        this.tabPage2 = new System.Windows.Forms.TabPage();
        this.tabControl1.SuspendLayout();
        this.SuspendLayout();
        // 
        // button1
        // 
        this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
        this.button1.Location = new System.Drawing.Point(247, 3);
        this.button1.Name = "button1";
        this.button1.Size = new System.Drawing.Size(23, 23);
        this.button1.TabIndex = 0;
        this.button1.Text = "+";
        this.button1.UseVisualStyleBackColor = true;
        this.button1.Click += new System.EventHandler(this.button1_Click);
        // 
        // tabControl1
        // 
        this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
        | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
        this.tabControl1.Controls.Add(this.tabPage1);
        this.tabControl1.Controls.Add(this.tabPage2);
        this.tabControl1.Location = new System.Drawing.Point(3, 3);
        this.tabControl1.Name = "tabControl1";
        this.tabControl1.SelectedIndex = 0;
        this.tabControl1.Size = new System.Drawing.Size(267, 181);
        this.tabControl1.TabIndex = 1;
        // 
        // tabPage1
        // 
        this.tabPage1.Location = new System.Drawing.Point(4, 25);
        this.tabPage1.Name = "tabPage1";
        this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
        this.tabPage1.Size = new System.Drawing.Size(259, 152);
        this.tabPage1.TabIndex = 0;
        this.tabPage1.Text = "tabPage1";
        this.tabPage1.UseVisualStyleBackColor = true;
        // 
        // tabPage2
        // 
        this.tabPage2.Location = new System.Drawing.Point(4, 25);
        this.tabPage2.Name = "tabPage2";
        this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
        this.tabPage2.Size = new System.Drawing.Size(192, 71);
        this.tabPage2.TabIndex = 1;
        this.tabPage2.Text = "tabPage2";
        this.tabPage2.UseVisualStyleBackColor = true;
        // 
        // TabControlWithAdd
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.Controls.Add(this.button1);
        this.Controls.Add(this.tabControl1);
        this.Name = "TabControlWithAdd";
        this.Size = new System.Drawing.Size(273, 187);
        this.tabControl1.ResumeLayout(false);
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.TabControl tabControl1;
    private System.Windows.Forms.TabPage tabPage1;
    private System.Windows.Forms.TabPage tabPage2;
}

TabControlWithAdd.cs:

public partial class TabControlWithAdd : UserControl
{
    public TabControlWithAdd()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        tabControl1.TabPages.Add("Tab " + (tabControl1.TabPages.Count + 1));
    }
}

The above uses Button, but of course you can use any other clickable control you like, including Label (e.g. if you don't want the button border appearance), to produce the visual effect you want.

Share:
13,915
Jack
Author by

Jack

Computer lover. :-) -- I'm not a native speaker of the english language. So, if you find any mistake what I have written, you are free to fix for me or tell me on. :)

Updated on June 06, 2022

Comments

  • Jack
    Jack almost 2 years

    I'm tring to make a tab control have a "x" (close button) and "+" (new tab button). I found a solution to add a x button, the tab looks like this now:

    enter image description here

    But I want to add a + where's that black circle right now. I have no idea how, I tried draw on Paint event of the last tab, like this:

    var p = tabs.TabPages[tabs.TabCount - 1];
    p.Paint += new PaintEventHandler(tab_OnDrawPage);
    
    private void tab_OnDrawPage(object sender, PaintEventArgs e)
    {
        // e.ClipRectangle.
        e.Graphics.DrawString("+", 
                              new Font("verdana", 
                                       10, 
                                       FontStyle.Bold), 
                              Brushes.Black, 
                              e.ClipRectangle.X + 10, 
                              e.ClipRectangle.Y + 10);
    }
    

    But it didn't show anything draw. I guess it has to do with the positions I passed to DrawString() call, but I don't know the proper ones to use. I used +10 to draw it away from last tab. How to fix that?. I haven't done any custom drawing myself, I'm learning it.

  • Reza Aghaei
    Reza Aghaei about 8 years
    @Jack, I guessed maybe you are not interested in the logic of drawing close icon, plus icon and closing tabs. So to keep the answer more clean, I shared only logic of preventing selection of last tab and check for click on last tab. let me know if you have any question about the answer :)
  • Jack
    Jack about 8 years
    I'm trying use this approach. I found a bug in the applicaiton, when you have only one tab also the "new tab" one, when you hit the "x" button the mouse automatically move to "new tab" tab e create a new one, that way, I can never delete all tabs unless I carefully hit x button and without leave mouse's click button drag cursor away from "new tab". The issue I guess is in the check this.tabControl1.GetTabRect(lastIndex).Contains(e.Location) that match all the tab and not only the "+" button (I'm using SizeMode = Fixed, so the tab is larger than the "+" icon). I didn't mange to fix, any idea?
  • Reza Aghaei
    Reza Aghaei about 8 years
    @Jack, I am on a mobile phone now, I'll check it tomorrow and will share the result with you. But I think I had not such issue. In mouse down check if you clicked in anywhere of the last tab, then perform adding and then return. Otherwise check for click on close button. This should work.
  • Reza Aghaei
    Reza Aghaei about 8 years
    @Jack I checked it. There is no problem in its behavior. I'll share the full code here.
  • Reza Aghaei
    Reza Aghaei about 8 years
    @Jack I shared an answer for this question WinForms TabControl - Add New Tab Button (+) which its focus is on having + button. Hope you find it helpful. It's independent from this question, but I also linked this post in my answer to have custom draw tabs.
  • Jack
    Jack about 8 years
    Your implementation doesn't really have the issue I previously mentioned. Works really great. Tab closing is working just fine now. Thanks. I handled MouseClick event rather MouseDown, as you have. Does it make any differenc? the issue with the last tab size still doesn't work for me and I can't see why. My last icon tab look like this: i.imgur.com/8koMZjQ.png SendMessage() always return value of 60
  • Reza Aghaei
    Reza Aghaei about 8 years
    I guess you have set text for last tab. Don't set a text for it, or just set a single character text for it. We didn't draw the text but it will occupy its required space. That send message trick set the minimum allowed size for tab. It doesn't make change on tabs with size larger than minimum. Mouse click is OK, I also prefer it and probably I'll edit the answer to use it.
  • Jack
    Jack about 8 years
    You're right, it was the tab's (default) text which that caused the issue. Thanks again
  • Jack
    Jack about 8 years
    I was wondering, how hard would be to make a rouded tabs like this Qt's one? i.imgur.com/xL5f0bq.png I need a really deep knowledge isn't?
  • Reza Aghaei
    Reza Aghaei about 8 years
    Thank you for your kindness :-) It should not be such hard, I've seen some good implementations in codeproject.com. Take a look at them, then you can customize the drawin code based on your requirements.