'Dynamically resize TabControl and Form width to the number of TabPages

I have a windows form with a TabControl and a ListView.
When I run the application, I want the Width of the TabControl to increase/decrease to show all the TabPages without horizontal scrollbar and have the Form resize it's Width accordingly, to insure that the TabControl and ListView are visible.

A screenshot is below.

enter image description here



Solution 1:[1]

To auto-size a TabControl to the size of its Headers, you need to calculate the width of the text of each Header. It's simpler in case the TabControl.SizeMode is set to Fixed, since you can set the ItemSize.Width and all Headers will have the same width.

If the TabControl.SizeMode is set to the default Normal, you have to measure the Text of each Header, adding 1px for the Border (2px if it's the second TabPage - small bug in the base Control).

In the first case, the size of the TabControl is:

tabControl1.Width = tabControl1.TabPages.Count * (tabControl1.ItemSize.Width + 1);

in the second case, measure the text of each Header using TextRendrer.MeasureText:

private int MeasureTabPagesWidth(TabControl tc)
{
    if (tc.TabPages.Count == 0) return tc.Width;
    int newWidth = 0;
    int border = tc.TabPages.Count == 2 ? 2 : 1;
    var flags = TextFormatFlags.LeftAndRightPadding;

    using (var g = tc.CreateGraphics()) {
        foreach (TabPage tab in tc.TabPages) {
            newWidth += TextRenderer.MeasureText(g, tab.Text, tc.Font, 
                new Size(int.MaxValue, tc.Font.Height + 4), flags).Width + border;
        }
    }
    return newWidth;
}

Setup the Layout:

  • Add a TableLayoutPanel to your Form, with one Row and two Columns (i.e., remove one Row)
  • Add the TabControl to the Cell on the left and the ListBox to the other Cell.
  • Set both Cells's style to AutoSize (after you have added your Controls).
  • Set the TableLayoutPanel to: AutoSize = true, AutoSizeMode = GrowAndShrink
  • Set the Form to auto-size in the same way
  • Set the Form's MinimumSize and MaximumSize. The former is usually set to the design size, the latter is up to you; you could use the current Screen WorkingArea as reference.
  • Calculate the new Width of the TabControl when the Form is created or loaded (i.e., in its Constructor or OnLoad() or Form.Load), so the Form will auto-size to the size of the TableLayoutPanel, whici in turn auto-sizes to the size of its child Controls.

Now you can add or remove TabPages at run-time and the Form will auto-size to the width you calculate in the TabControl.ControlAdded and TabControl.ControlRemoved event handlers (also checking whether the Control added is of Type TabPage).

Example:

  • The MeasureTabPagesWidth() method is the one shown above.
  • The TableLayoutPanel is named tlp1
  • The TabControl is named tabControl1
  • The Buttons used in the visual example have names that define their role.
public partial class AutoSizeForm : Form
{
    public AutoSizeForm()
    {
        InitializeComponent();
        tabControl1.Width = MeasureTabPagesWidth(tabControl1);
    }

    private void tabControl1_ControlAdded(object sender, ControlEventArgs e)
    {
        // Event notified after the TabPage has been added
        if (e.Control is TabPage) {
            tabControl1.Width = MeasureTabPagesWidth(tabControl1);
        }
    }

    private void tabControl1_ControlRemoved(object sender, ControlEventArgs e)
    {
        if (e.Control is TabPage) {
            // Use deferred execution, since the TabPage is removed after 
            // the event handler method completes.
            BeginInvoke(new Action(()=> tabControl1.Width = MeasureTabPagesWidth(tabControl1)));
        }
    }

    private void btnAddPage_Click(object sender, EventArgs e) 
    {
        tabControl1.TabPages.Add(new TabPage("New TabpPage Text"));
    }

    private void btnRemovePage_Click(object sender, EventArgs e)
    {
        if (tabControl1.TabPages.Count > 0) {
            tabControl1.TabPages.RemoveAt(tabControl1.TabPages.Count - 1);
        }
    }

    private void btnAddCtlToTLP_Click(object sender, EventArgs e)
    {
        tlp1.ColumnCount += 1;
        tlp1.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
        var mc = new MonthCalendar();
        tlp1.SetColumn(mc, tlp1.ColumnCount - 1);
        tlp1.Controls.Add(mc);
    }
}

This is how it works:
Tested in Windows 7, since this appears to be the System in use

Form TabControl AutoSize

Sample Project:
Sample Project on Google Drive (.Net Framework 4.8 - C# 7.3)
Rebuild the Solution before running

Solution 2:[2]

Starting form for this example

Starting out with that form, I'm going to add 8 tabs at run-time, calculate width of the text in the tabs + padding size x2 (both sides of the tabs) and then resize controls as needed.

   public Form1()
    {
        InitializeComponent();
        //Clear our default tabs.
        tabControl1.TabPages.Clear();
        //Add more tabs than would be visible by default
        for (int i=1;i<=8;i++)
        {
            tabControl1.TabPages.Add("Tab " + i.ToString());
        }
        ResizeTabControl();
        ResizeListViewControl();
        ResizeForm();
    }
    void ResizeTabControl()
    {
        int tabCount = tabControl1.TabCount;

        float length = 0;
        using (Graphics g = CreateGraphics())
        {
            //Iterate through the tabs and get the length of the text.
            for (int i = 0; i <= tabCount - 1; i++)
                length += g.MeasureString(tabControl1.TabPages[i].Text, tabControl1.Font).Width;
        }
        //Resize the tab control where X is the length of all text in the tabs plus padding x 2 x total tabs.
        tabControl1.Size = new Size(Convert.ToInt32(length) + (tabCount * 2 * tabControl1.Padding.X), tabControl1.Width);          
    }
    void ResizeListViewControl()
    {
        //Move listview 10 pixels away from tabcontrol's edge
        listView1.Location = new Point(tabControl1.Location.X + tabControl1.Width + 10, listView1.Location.Y);
    }
    void ResizeForm()
    {
        //Resize form to accomodate changes.
        this.Width = listView1.Location.X + listView1.Width + 20;
    }

After it's all said and done, this is what it looks like:

And with 20 tabs, because why not.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Mikael