Make an owner-drawn TabControl in C#

example

This example shows a couple of techniques including how to make an owner-drawn TabControl and how to let the user close tabs at run time.

The program uses the following Load event handler to get the TabControl ready for use.

private void Form1_Load(object sender, EventArgs e)
{
    // We will draw the tabs.
    tabMenu.DrawMode = TabDrawMode.OwnerDrawFixed;

    // SizeMode must be Fixed to change tab size.
    tabMenu.SizeMode = TabSizeMode.Fixed;

    // Set the size for the tabs.
    Size tab_size = tabMenu.ItemSize;
    tab_size.Width = 100;
    tab_size.Height += 6;
    tabMenu.ItemSize = tab_size;
}

This code sets the TabControl‘s DrawMode property to OwnerDrawFixed. The SizeMode must be Fixed if the program needs to change the tab size. This example does change the size to 10 pixels wide and 6 pixels taller than the initial default.

When the control needs to draw a tab, it raises its DrawItem event. The following code shows how the program’s DrawItem event handler draws the tabs.

// The size of the X in each tab's upper right corner.
private int Xwid = 8;
private const int tab_margin = 3;

// Draw a tab.
private void tabMenu_DrawItem(object sender, DrawItemEventArgs e)
{
    Brush txt_brush, box_brush;
    Pen box_pen;

    // We draw in the TabRect rather than on e.Bounds
    // so we can use TabRect later in MouseDown.
    Rectangle tab_rect = tabMenu.GetTabRect(e.Index);

    // Draw the background.
    // Pick appropriate pens and brushes.
    if (e.State == DrawItemState.Selected)
    {
        e.Graphics.FillRectangle(Brushes.DarkOrange, tab_rect);
        e.DrawFocusRectangle();

        txt_brush = Brushes.Yellow;
        box_brush = Brushes.Silver;
        box_pen = Pens.DarkBlue;
    }
    else
    {
        e.Graphics.FillRectangle(Brushes.PaleGreen, tab_rect);

        txt_brush = Brushes.DarkBlue;
        box_brush = Brushes.LightGray;
        box_pen = Pens.DarkBlue;
    }

    // Allow room for margins.
    RectangleF layout_rect = new RectangleF(
        tab_rect.Left + tab_margin,
        tab_rect.Y + tab_margin,
        tab_rect.Width - 2 * tab_margin,
        tab_rect.Height - 2 * tab_margin);
    using (StringFormat string_format = new StringFormat())
    {
        // Draw the tab # in the upper left corner.
        using (Font small_font = new Font(this.Font.FontFamily,
            6, FontStyle.Bold))
        {
            string_format.Alignment = StringAlignment.Near;
            string_format.LineAlignment = StringAlignment.Near;
            e.Graphics.DrawString(
                e.Index.ToString(),
                small_font,
                txt_brush,
                layout_rect,
                string_format);
        }

        // Draw the tab's text centered.
        using (Font big_font = new Font(this.Font, FontStyle.Bold))
        {
            string_format.Alignment = StringAlignment.Center;
            string_format.LineAlignment = StringAlignment.Center;
            e.Graphics.DrawString(
                tabMenu.TabPages[e.Index].Text,
                big_font,
                txt_brush,
                layout_rect,
                string_format);
        }

        // Draw an X in the upper right corner.
        Rectangle rect = tabMenu.GetTabRect(e.Index);
        e.Graphics.FillRectangle(box_brush,
            layout_rect.Right - Xwid,
            layout_rect.Top,
            Xwid,
            Xwid);
        e.Graphics.DrawRectangle(box_pen,
            layout_rect.Right - Xwid,
            layout_rect.Top,
            Xwid,
            Xwid);
        e.Graphics.DrawLine(box_pen,
            layout_rect.Right - Xwid,
            layout_rect.Top,
            layout_rect.Right,
            layout_rect.Top + Xwid);
        e.Graphics.DrawLine(box_pen,
            layout_rect.Right - Xwid,
            layout_rect.Top + Xwid,
            layout_rect.Right,
            layout_rect.Top);
    }
}

The event handler gets the tab’s area by using the control’s GetTabRect method. This returns a rectangle that is slightly different from the one in the e.Bounds parameter and is the rectangle that we can use later in the MouseDown event handler.

DrawItem then checks the e.State parameter to see whether the tab is selected and picks colors accordingly. It fills the tab’s background and, if the tab is selected, it draws a focus rectangle on it.

Next the code makes a rectangle representing the tab’s area minus some margins. It uses this rectangle to position the tab’s number in the upper left corner and the tab’s text centered. The program then draws an X in the upper right corner.

When the user presses the mouse down on a tab, the following MouseDown event handler executes.

// If the mouse is over an X, close the tab.
private void tabMenu_MouseDown(object sender, MouseEventArgs e)
{
    // See if this is over a tab.
    for (int i = 0; i < tabMenu.TabPages.Count; i++)
    {
        // Get the TabRect plus room for margins.
        Rectangle tab_rect = tabMenu.GetTabRect(i);
        RectangleF rect = new RectangleF(
            tab_rect.Left + tab_margin,
            tab_rect.Y + tab_margin,
            tab_rect.Width - 2 * tab_margin,
            tab_rect.Height - 2 * tab_margin);
        if (e.X >= rect.Right - Xwid &&
            e.X <= rect.Right &&
            e.Y >= rect.Top &&
            e.Y <= rect.Top + Xwid)
        {
            Console.WriteLine("Tab " + i);
            tabMenu.TabPages.RemoveAt(i);
            return;
        }
    }
}

This code determines whether the mouse is over any tab’s X. If the mouse is over an X, the program removes the corresponding tab.

You could use similar techniques to let the user add tabs (much as web browsers let you add new tabs), but that begs the question of what you would put on them.

See this updated post: Make an improved owner-drawn TabControl in C#.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in controls and tagged , , , , , , , , , . Bookmark the permalink.

One Response to Make an owner-drawn TabControl in C#

  1. Pingback: Use TabControl accelerators in C# - C# HelperC# Helper

Leave a Reply

Your email address will not be published. Required fields are marked *