Click here to Skip to main content
13,249,813 members (65,417 online)
Click here to Skip to main content
Add your own
alternative version


162 bookmarked
Posted 28 Dec 2005

Y(et)A(nother)TabControl: A Custom Tab Control With Owner Drawn Tabs

, 6 May 2013
Rate this:
Please Sign up or sign in to vote.
A foray into the world of creating composite custom controls with design-time support.

Table of contents  

What are you thinking, Curtis?

Yep, I've written yet another TabControl-like control. I had a transparent motive for doing so: user-drawn tabs. The System.Windows.Forms.TabControl in the 1.1 Framework has a DrawMode property that allows the user to draw the controls; however, the tabs get drawn with a fixed size and I didn't like that. Moreover, I wanted to plug in different drawing capabilities based on the style that I needed.

Oh, and I thought it would be cool to roll my own control. I hope that you can learn from this code and have fun with it. 

The legal stuff 

I have released the GrayIris.Utilities.dll under the Modified BSD License which I reproduce here for your viewing pleasure:

Copyright (c) 2005, Gray Iris Software LC
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

  Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.

  Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

  Neither the name of Gray Iris Software LC nor the names of its contributors
  may be used to endorse or promote products derived from this software
  without specific prior written permission.


Features of the YaTabControl

I designed the YaTabControl to have the following features:

  • Fully-customizable and easy-to-use owner-drawn tabs;
  • Fully exposed coloring;
  • Hideable scroll buttons;
  • Tabs docked to any of the four cardinal directions;
  • A cancelable TabChanging event;
  • Design-time support; and,
  • ImageList support.
  • Three supplied tab drawing classes:
    • VsTabDrawer: Draws tabs like the ones in Visual Studio.
    • XlTabDrawer: Draws tabs like the ones in Microsoft Excel.
    • OvalTabDrawer: Draws the active tab as an oval.

Essentially, I wanted a nicer version of the System.Windows.Forms.TabControl with more functionality.

The challenges

I had three major challenges in writing this control:

  • Providing a simple-to-use capability for customizing the tabs;
  • Getting the controls to stay where they should in the tab pages; and,
  • Creating the designer to provide interactivity in the design-time environment.

Drawing the tabs easily

Because I wanted the YaTabControl to have the ability to display the tabs docked to the four cardinal directions, I needed a way to provide an easy-to-use mechanism for drawing the tabs. After giving it some careful thought, I decided that, regardless of the orientation of the tabs, the mechanism that actually draws the tabs should start drawing at (0,0) and extend the length of the tab.

Accomplishing this required a two-step procedure. First, I created an abstract base class with, among other things, the following method:

public abstract void DrawTab( Color foreColor,
                              Color backColor,
                              Color highlightColor,
                              Color shadowColor,
                              Color borderColor,
                              bool active,
                              bool mouseOver,
                              DockStyle dock,
                              Graphics graphics,
                              SizeF tabSize );

This method would get called to actually draw the tab given the Colors, its active status, the Graphics on which to draw, and the tabSize. The implementation would start drawing at (0,0) and extend within the given SizeF (or not, if overlap is desired).

To provide that ease-of-use, the YaTabControl overrides its OnPaint method. The Graphics supplied to the method gets passed to the specified tab drawing object; however, some magic happens first to provide the tab-ignorant drawing. The class first applies a RotateTransform so that the tabs get drawn in the correct orientation. Then, when the tab drawer gets called to draw the nth tab, a TranslateTransform gets applied to the Graphics object so that the coordinate (0,0) exists at the upper-left corner of where the tab should get drawn.

Because of this design, the tab drawing class need not know the direction the tab needs to face, does not need to translate coordinates on its own, and does not need to handle any of the other messy details needed to draw on the Graphics' surface.

You may note, though, that a DockStyle gets passed into the method. This exists so that if the tab drawing method would like to draw highlights and shadows, it knows which direction the tab faces. This information gets used in the VsTabDrawer class included with the source. The tabs in Visual Studio have highlights and shadows. Therefore, the drawing class needs to know on which side the tabs have docked to draw the appropriate shadows/highlights.

Getting controls to stay where they belong

My next problem revolved around the placement of controls. I investigated how the TabControl acted when in use at runtime. The TabControl's Controls collection held only the TabPages and only one TabPage had a true Visible property at any given time. I wanted to emulate this functionality, as well as provide a margin around the area in which the tab pages would get drawn.

After performing many trials (and getting many errors), I hit upon a solution that worked very well for me. I will detail it for you in the following list.

  • Created the YaTabControl.ControlCollection inner class inherited from Control.ControlCollection. Overrode the following methods from the base class:
    • Constructor: Throws an ArgumentException if the owning control does not have YaTabControl in its inheritance chain.
    • Add( Control ): Throws an ArgumentException if the Control getting added to the collection does not have YaTabPage in its inheritance chain.
  • Overrode the YaTabControl.CreateControlsInstance() method to return a new YaTabControl.ControlCollection.
  • Overrode the YaTabControl.DisplayRectangle property to return a Rectangle that described the area in which the YaTabPage should get drawn.
  • Created the YaTabPage.ControlCollection inner class inherited from Control.ControlCollection. Overrode the following methods from the base class:
    • Constructor: Throws an ArgumentException if the owning control does not have YaTabPage in its inheritance chain.
    • Add( Control ): Throws an ArgumentException if the Control getting added to the collection does have YaTabPage in its inheritance chain.
  • Overrode the YaTabPage.CreateControlsInstance() method to return a new YaTabPage.ControlCollection.
  • Overrode the YaTabPage.DockStyle property to ignore all attempts to set the DockStyle. In doing so, the YaTabPage always has a DockStyle of Fill.

With those implementations in place, only YaTabPages can get added to the YaTabControl and the YaTabPages always have a DockStyle of Fill which means that, when the runtime draws the YaTabPages, they fill the Rectangle returned by YaTabControl.DisplayRectangle.

Design-time support

Once I got the YaTabControl working the way I wanted it to work in the run-time environment, I fired up the Forms Designer View, dropped a YaTabControl onto a form, and found that nothing worked. Nothing. I couldn't add YaTabPages to it. I could not change tabs once I did add them. I could not scroll the tabs. I could do nothing! I growled, cursed, drank some coffee, and went to Google to find some help.

Getting the YaTabControl to work

I should have just stayed on CodeProject. In the section "Design-time integration" of A designable PropertyTree for VS.NET by Russell Morris, I found all the information I needed. If you have developed a control and want design-time support, I recommend reading that section of his article, as well as the articles to which he links.

Rather than rehash good code, I point you to the details in the implementation of the GrayIris.Utilities.Controls.Design.YaTabControlDesigner class. I will write about the most difficult portion: getting the mouse clicks in the design-time environment to change tabs. I overrode the ControlDesigner.WndProc( Message ) method to intercept mouse clicks and, if they exist in the correct places, to perform the appropriate methods on the underlying YaTabControl.

Getting the YaTabDrawer to work

I initially designed the YaTabControl to use a class that implemented the IYaTabDrawer interface to draw the tabs. However, that just doesn't work in the design-time environment the way I wanted it to work. So, I changed the IYaTabDrawer interface to an abstract base class YaTabDrawer that inherits from Component. Now, classes that support drawing tabs can get dropped into the design-time environment just like Timers and OpenFileDialogs. You can see all this in the screenshot included in the Dropping It In section of this article.

Writing your own tab drawer

I don't think that an article on CodeProject would feel complete without just a little bit of code. Since I expect the developers that use this control will want to implement their own tab drawing classes, I thought I would give an example of that process.

The following list describes the questions that you should answer before you continue with the implementation:

  • Will your tabs use highlights and shadows when drawing the tab?
  • Should your tabs get docked to all sides, or just a specific subset of Left, Right, Top, and Bottom?
  • What shape are your tabs?

After you answer those questions, you are about half-way there.

As an example, I will develop the LineAndBoxTabDrawer class. It will make a pretty band of color across the region, and the active tab will have a box around it:

Using my checklist above, this tab drawer will not use highlights and can get docked on any four sides. So, I create a class inherited from YaTabDrawer and override the appropriate method and properties:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace GrayIris.Utilities.UI.Controls
  /// <summary>
  /// Draws a pretty tab and a pretty line.
  /// </summary>
  public class LineAndBoxTabDrawer : YaTabDrawer
    /// <summary>
    /// Creates a new instance of the <see cref="LineAndBoxTabDrawer"/>
    /// class.
    /// </summary>
    public LineAndBoxTabDrawer() {}

    /// <summary>
    /// Overridden. Inherited from <see cref="YaTabDrawer"/>.
    /// </summary>
    public override DockStyle[] SupportedTabDockStyles
        return new DockStyle[] { DockStyle.Bottom, 
              DockStyle.Top, DockStyle.Left, DockStyle.Right };

    /// <summary>
    /// Overridden. Inherited from <see cref="YaTabDrawer"/>.
    /// </summary>
    /// <param name="dock">
    /// See <see cref="YaTabDrawer.SupportsTabDockStyle( DockStyle )"/>.
    /// </param>
    /// <returns>
    /// Returns <b>true</b> for any <see cref="DockStyle"/> in one
    /// of the four cardinal directions.
    /// </returns>
    public override bool SupportsTabDockStyle( DockStyle dock )
      return dock != DockStyle.None && dock != DockStyle.Fill;

    /// <summary>
    /// Overridden. Inherited from <see cref="YaTabDrawer"/>.
    /// </summary>
    public override bool UsesHighlghts
        return false;

Of course, it won't compile. I haven't implemented the drawing portion. So, in the previous class, I write the following method:

/// <summary>
/// Overridden. Inherited from <see cref="YaTabDrawer"/>.
/// </summary>
/// <param name="foreColor">See <see cref="YaTabDrawer"/>.</param>
/// <param name="backColor">See <see cref="YaTabDrawer"/>.</param>
/// <param name="highlightColor">See <see cref="YaTabDrawer"/>.</param>
/// <param name="shadowColor">See <see cref="YaTabDrawer"/>.</param>
/// <param name="borderColor">See <see cref="YaTabDrawer"/>.</param>
/// <param name="active">See <see cref="YaTabDrawer"/>.</param>
/// <param name="mouseOver">See <see cref="YaTabDrawer"/>.</param>
/// <param name="dock">See <see cref="YaTabDrawer"/>.</param>
/// <param name="graphics">See <see cref="YaTabDrawer"/>.</param>
/// <param name="tabSize">See <see cref="YaTabDrawer"/>.</param>
public override void DrawTab( Color foreColor,
                              Color backColor,
                              Color highlightColor,
                              Color shadowColor,
                              Color borderColor,
                              bool active,
                              bool mouseOver,
                              DockStyle dock,
                              Graphics graphics,
                              SizeF tabSize )
  if( active )
    Pen p = new Pen( borderColor );
    graphics.DrawRectangle( p, 0, 0, tabSize.Width, tabSize.Height );
    Brush b = Brushes.Peru;
    float dif = tabSize.Height / 4.0f;
    RectangleF r = new RectangleF( 0.0f, dif,
         tabSize.Width, tabSize.Height - dif - dif );
    graphics.FillRectangle( b, r );

So, with 16 net lines of code, I have implemented a custom tab drawing class for the YaTabControl. The above code provides tabs as in the following screenshot:

Obviously, we could do a lot of cool things with this tab drawer. We could expose the color of the band, the percentage of the available height to use, and more. You can knock yourself out, kid!

Dropping it in

As I wrote about in Design-Time Support, I spent a lot of time developing the functionality to use the YaTabControl in the Form Designer environment. To use it, all you need to do is add the control into the toolbox, drag and drop it to your control, and customize away!


  • 17 Feb 2005 - Source code updated.
  • 6 Feb 2006 - Fixed a "leak" with undisposed Pen with patch from bschurter.
  • 14 Dec 2010 - Fixed SelectedIndex with patch from Ondra.
  • 25 Apr 2013 - Code moved to GitHub and mouseover code added.


This article, along with any associated source code and files, is licensed under The BSD License


About the Author

Curtis Schlak.
United States United States
No Biography provided

You may also be interested in...


Comments and Discussions

GeneralRe: Mac Style Tab Controls Pin
GamePlanner6-Aug-08 3:02
memberGamePlanner6-Aug-08 3:02 
GeneralRe: Mac Style Tab Controls Pin
Curtis Schlak.31-Oct-08 10:26
memberCurtis Schlak.31-Oct-08 10:26 
GeneralNavigation arrows Pin
Jim Weiler21-Sep-07 7:13
memberJim Weiler21-Sep-07 7:13 
GeneralRe: Navigation arrows Pin
Curtis S.17-Apr-08 6:42
memberCurtis S.17-Apr-08 6:42 
QuestionCustom tab control Pin
kiran kharat4-Sep-07 1:58
memberkiran kharat4-Sep-07 1:58 
GeneralRe: Custom tab control Pin
Curtis S.28-Apr-08 18:11
memberCurtis S.28-Apr-08 18:11 
QuestionWhat a waste of time Pin
Member #37230954-Feb-07 14:14
memberMember #37230954-Feb-07 14:14 
AnswerRe: What a waste of time Pin
Christian Graus4-Feb-07 14:55
staffChristian Graus4-Feb-07 14:55 
AnswerRe: What a waste of time Pin
M Aamir Maniar20-Feb-07 18:58
memberM Aamir Maniar20-Feb-07 18:58 
AnswerRe: What a waste of time Pin
Curtis S.28-Apr-08 18:16
memberCurtis S.28-Apr-08 18:16 
AnswerRe: What a waste of time Pin
big_jimmy25-Feb-09 0:43
memberbig_jimmy25-Feb-09 0:43 
AnswerRe: What a waste of time Pin
pmorrisweb6-Oct-11 6:12
memberpmorrisweb6-Oct-11 6:12 
GeneralIs it up to .Net 2.0 yet Pin
UltraWhack4-Jan-07 11:52
memberUltraWhack4-Jan-07 11:52 
Generalgreate control !! but there is a small error n fix for it Pin
kavicol18-Sep-06 1:08
memberkavicol18-Sep-06 1:08 
seems to be this is a greate contril.
just downloaded and started to work on it. i will u give full marks for this 4 sure

hi guys only now i found a small error. you can't set the margin property. so you have to 'Margin' property as 'TabMargin' in the sourc. then it will work fine



GeneralRe: greate control !! but there is a small error n fix for it Pin
vali_cd10-Jul-08 22:43
membervali_cd10-Jul-08 22:43 
GeneralSuperb! Pin
lbenini22-Jun-06 0:43
memberlbenini22-Jun-06 0:43 
GeneralRe: Superb! Pin
Curtis S.23-Jun-06 16:50
memberCurtis S.23-Jun-06 16:50 
GeneralDemo Project Source Pin
Manoj Mathew2-May-06 9:50
memberManoj Mathew2-May-06 9:50 
GeneralRe: Demo Project Source Pin
Curtis S.4-May-06 3:06
memberCurtis S.4-May-06 3:06 
GeneralRe: Demo Project Source Pin
Manoj Mathew8-May-06 9:13
memberManoj Mathew8-May-06 9:13 
GeneralLocalization Problem Pin
Alaa Jbara4-Mar-06 23:29
memberAlaa Jbara4-Mar-06 23:29 
Question.NET CF - will it work? Pin
trydis1-Mar-06 3:20
membertrydis1-Mar-06 3:20 
AnswerRe: .NET CF - will it work? Pin
Curtis S.4-May-06 3:03
memberCurtis S.4-May-06 3:03 
GeneralTabDockStyle.Left &amp;amp; TabDockStyle.Right [modified] Pin
Volx21-Feb-06 10:24
memberVolx21-Feb-06 10:24 
GeneralRe: TabDockStyle.Left & TabDockStyle.Right Pin
Curtis S.24-Feb-06 8:35
memberCurtis S.24-Feb-06 8:35 
QuestionWhat about licensing? Pin
Win32nipuh10-Feb-06 3:12
memberWin32nipuh10-Feb-06 3:12 
AnswerRe: What about licensing? Pin
Curtis S.14-Feb-06 5:25
memberCurtis S.14-Feb-06 5:25 
QuestionHow to bold the text on the tab? Pin
Henk van der Geld3-Feb-06 1:15
memberHenk van der Geld3-Feb-06 1:15 
AnswerRe: How to bold the text on the tab? Pin
Curtis S.3-Feb-06 3:14
memberCurtis S.3-Feb-06 3:14 
AnswerRe: How to bold the text on the tab? Pin
Sreyas M N18-Nov-09 6:32
memberSreyas M N18-Nov-09 6:32 
GeneralRe: How to bold the text on the tab? Pin
Henk van der Geld18-Nov-09 10:30
memberHenk van der Geld18-Nov-09 10:30 
GeneralPretty good but... Pin
kiwidude20-Jan-06 5:15
memberkiwidude20-Jan-06 5:15 
GeneralRe: Pretty good but... Pin
Curtis S.20-Jan-06 6:38
memberCurtis S.20-Jan-06 6:38 
GeneralRe: Pretty good but... Pin
kiwidude20-Jan-06 7:16
memberkiwidude20-Jan-06 7:16 
GeneralRe: Pretty good but... Pin
Curtis S.20-Jan-06 14:25
memberCurtis S.20-Jan-06 14:25 
GeneralRe: Pretty good but... Pin
bschurter15-Feb-06 15:11
memberbschurter15-Feb-06 15:11 
GeneralRe: Pretty good but... Pin
Curtis S.15-Feb-06 16:28
memberCurtis S.15-Feb-06 16:28 
GeneralRe: Pretty good but... Pin
bschurter16-Feb-06 3:59
memberbschurter16-Feb-06 3:59 
GeneralRe: Pretty good but... Pin
Curtis S.16-Feb-06 15:06
memberCurtis S.16-Feb-06 15:06 
GeneralKudos Pin
tonyt29-Dec-05 4:35
membertonyt29-Dec-05 4:35 
GeneralRe: Kudos Pin
Curtis S.29-Dec-05 6:18
memberCurtis S.29-Dec-05 6:18 
GeneralRe: Kudos Pin
tonyt29-Dec-05 23:05
membertonyt29-Dec-05 23:05 
GeneralRe: Kudos Pin
Curtis S.30-Dec-05 5:41
memberCurtis S.30-Dec-05 5:41 
Generalvery good! Pin
zino29-Dec-05 1:03
memberzino29-Dec-05 1:03 
GeneralRe: very good! Pin
Curtis S.29-Dec-05 4:21
memberCurtis S.29-Dec-05 4:21 
GeneralVery nice! Pin
Dan Letecky28-Dec-05 13:23
memberDan Letecky28-Dec-05 13:23 
GeneralRe: Very nice! Pin
Curtis S.28-Dec-05 13:42
memberCurtis S.28-Dec-05 13:42 
GeneralRe: Very nice! Pin
Dan Letecky28-Dec-05 13:53
memberDan Letecky28-Dec-05 13:53 
GeneralRe: Very nice! Pin
Curtis S.28-Dec-05 14:03
memberCurtis S.28-Dec-05 14:03 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.171114.1 | Last Updated 6 May 2013
Article Copyright 2005 by Curtis Schlak.
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid