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.

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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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:

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.

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 trueVisible 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.

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.

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:

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!

Comments and Discussions

Hi, I like this tabcontrol however with the stock tabcontrol i can use the code:mainform.mainformTabControl.TabPages.Add(tab)Sorry if its just me being a noob but how would this be done with YaTabControl?Cheers

Best Tab control, i've try to use it. btw there is a bug in YaTabControl.cs file on OnMouseMove method. i always got error when all tabs is removed at Runtime and mouse click at another control, so i just add if block to check Controls Count inside that method.

Could you please add to your Update History what has been changed since 2005 ?

thanks,Bill

Humans are amphibians: half spirit, half animal; as spirits they belong to the eternal world; as animals they inhabit time. While their spirit can be directed to an eternal object, their bodies, passions, and imagination are in continual change, for to be in time, means to change. Their nearest approach to constancy is undulation: repeated return to a level from which they repeatedly fall back, a series of troughs and peaks.” C.S. Lewis

Just got done adding a new flag "mouseOver" to the YaTabDrawer abstract class. Download the code from GitHub[^], build, and run the Example project in there. The Excel-like tab drawer now makes the tab under the cursor orange in reaction to the value of the "mouseOver" flag.

"we must lose precision to make significant statements about complex systems."-deKorvin on uncertainty

This is a great control.I've added the dll in the control box, but i don't know how to view the control in the design mode. Can you please let me know how to view this control in design mode, is there is any steps that I need follow?I' am getting the below error when I dropped the control over the form in design mode.

"Tried to use the YaTabControlDesigner with a class that does not inherit from YaTabControl.