Click here to Skip to main content
15,860,943 members
Articles / Programming Languages / C#

vtTab: Tab Control Extender Class

Rate me:
Please Sign up or sign in to vote.
4.81/5 (17 votes)
25 May 2009CPOL4 min read 71K   2.3K   60   20
A class used to extend the visual appearance of the tab control
Image 1

The Problem with UserControls is...

Some years ago, when I was coding in VB6, a friend asked me to help resolve a problem with a grid control he had written. This was a very elaborate and well written uc, ran without issues, except.. when the OS was an eastern European language version. For reasons that I don't think were ever discovered, it would freeze the application at random intervals, (cross threading? subclassing?). The point is that you can spend months writing the perfect user control, really put your heart into it, with the most pristine and elegant code behind it, but you just never know...

Another problem is that usercontrols like the one mentioned take a lot of time to write and perfect. I have written a grid that took two months and 27 thousand lines of code to complete, it is simply too much work to be practical.

This is why I started skinning native controls, it makes the most sense. Many of the controls in your OS have been around for years and have withstood the test of time. The usercontrols packaged with Visual Studio are implementations of these MFC control classes, neatly packaged and presented for use within the IDE. There is a problem however and that is...

The Good, the Bad, and the VS Dev Team

I would really hate to be one of the guys who has to read their 'fan mail', my heart goes out to them, really. Just about every usercontrol in Visual Studio either has a bug, is crippled in some way, or its appearance is sub-par. The Tab Control is a good example: There is no visual style if the control is vertically aligned, but worse than that, if the tab headers are aligned to the bottom of the control, the last two pixels are cut off (in Vista any ways).

How is it, that after all these versions of Visual Studio, the same annoying bugs are still there!? I mean, surely they know about it by now.

Well, I have a theory about that. Microsoft seems to often work towards opposing goals, they want better security, but they want a high level of automation, they want technology to evolve, but they crush competing ideas, etc. I believe this is one of those instances. They want us all to write applications with Visual Studio, (and so more software ported to Windows), but they don't want you to write better competing software. This has been going on forever, API that is not documented, (some calls in ntdll have actually been named only with numbers), limiting access to features within libraries, and of course, sticking us with crippled, buggy usercontrols.

And Now for Something Completely Different

Ok, now that I have that out of my system, (and so long as this article doesn't 'vanish', my IP isn't blackholed, and my electricity stays on, gulp), let's forge ahead..

I started writing a skinning library about a month ago (I'll release it next month), of which the cTabControl is a member class. My hope is that a single library will be able to customize the appearance of most of the common controls, doing away with the need for some usercontrols, and shortening the development cycle.

Why Use Bitmaps?

The main reason is that blitting an image to a dc is far faster and consumes fewer resources than constantly recreating the background via gradients or fills. Another good reason is versatility, many styles already exist on popular skinning sites such as wincustomize.com. You can choose a visual style that would work well with your application, and (with permission), extract the desired bitmaps and port them to the control. But, for the sake of completeness, I also added a custom drawing style to the class.

Using a Paint Struct to Control Painting

The BeginPaint/EndPaint API allow for a precise control of the painting process. Without these calls, you would only be overpainting the control, causing a lot of flicker. The ValidateRect call tells the operating system that no further painting is required in this area.

C#
PAINTSTRUCT pntStrct = new PAINTSTRUCT();
switch (m.Msg)
{
case WM_PAINT:
if (!_bPainting)
{
_bPainting = true;
// start painting engine
BeginPaint(m.HWnd, ref pntStrct);
drawTabControl();
ValidateRect(m.HWnd, ref pntStrct.rcPaint);
// done
EndPaint(m.HWnd, ref pntStrct);
_bPainting = false;
}
else
{
base.WndProc(ref m);
}
break;

Using SendMessage to Get the Tab Header Size

There just doesn't seem to be another way to do this. With all those control fields available, and nobody thought of slipping this in?

C#
SendMessage(_tabControlWnd, TCM_GETITEMRECT, i, ref tabRect);

headerRect = new Rectangle(tabRect.Left, tabRect.Top, 
		tabRect.Right - tabRect.Left, tabRect.Bottom - tabRect.Top);

Some Graphics Code

One of the problems with using images is that they don't always respond well to stretching, particularly the borders. You can solve this by drawing the borders separately, and stretching them on the bias as border elements.

C#
// to maintain a constant border depth while stretching the bitmap //
if (align == TabAlignment.Bottom || align == TabAlignment.Top)
bm = new Bitmap(tabRect.Right - tabRect.Left, xsize);
else
bm = new Bitmap(xsize, tabRect.Right - tabRect.Left);
Graphics gcl = Graphics.FromImage(bm);
// clone the inner portion
cl = _tabHeaderBitmap.Clone(new Rectangle((state * width) + 2, 2, 
	width - 4, _tabHeaderBitmap.Height - 2), 
	System.Drawing.Imaging.PixelFormat.DontCare);
// draw to new bmp
if (align == TabAlignment.Bottom || align == TabAlignment.Top)
gcl.DrawImage(cl, new Rectangle(2, 2, tabRect.Right - tabRect.Left, xsize));
else
gcl.DrawImage(cl, new Rectangle(2, 2, xsize, tabRect.Right - tabRect.Left));
// clone and draw the edges
// left
cl = _tabHeaderBitmap.Clone(new Rectangle(state * width, 0, 2, 
	_tabHeaderBitmap.Height), System.Drawing.Imaging.PixelFormat.DontCare);
gcl.DrawImage(cl, new Rectangle(0, 0, 2, xsize));
cl.Dispose();
// top
cl = _tabHeaderBitmap.Clone(new Rectangle(state * width + 2, 0, 
	width - 4, 2), System.Drawing.Imaging.PixelFormat.DontCare);
gcl.DrawImage(cl, new Rectangle(2, 0, bm.Width - 4, 2));
cl.Dispose();
//right
cl = _tabHeaderBitmap.Clone(new Rectangle((state * width) + 
	(width - 2), 0, 2, _tabHeaderBitmap.Height), 
	System.Drawing.Imaging.PixelFormat.DontCare);
gcl.DrawImage(cl, new Rectangle(bm.Width - 2, 0, 2, xsize));
cl.Dispose();
gcl.Dispose();

How to Rotate a Bitmap

Sounds easy, right... but during my research I found answers varying from a complete redraw with SetPixel, to using a Matrix to transform the image.

C#
case TabAlignment.Bottom:
bm.RotateFlip(RotateFlipType.Rotate180FlipX);
case TabAlignment.Left:

bm.RotateFlip(RotateFlipType.Rotate90FlipX);
...

Creating the Bitmaps

This is very straightforward if you are even casually familiar with an image processor. I used Fireworks and copied the images from screenshots. Below is the layout required:

tabstates.jpg

Style Examples

Bitmap Styles

Included are two examples of using a bitmap, an Internet Explorer 7, and a Vista like look:

immode.jpg

Custom Drawn

I also included a Vista style and a gradient in the custom drawn example.

cdmode.jpg

The class also includes an upgrade to the ToolTip with an internal tip class.

tooltip.jpg

History

  • 25th May, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Network Administrator vtdev.com
Canada Canada
Network and programming specialist. Started in C, and have learned about 14 languages since then. Cisco programmer, and lately writing a lot of C# and WPF code, (learning Java too). If I can dream it up, I can probably put it to code. My software company, (VTDev), is on the verge of releasing a couple of very cool things.. keep you posted.

Comments and Discussions

 
QuestionSteppenwolfe: Does your TabControl SendMessage report same sizes & location for diff. Rows? Pin
cyprussun20-Aug-12 16:20
cyprussun20-Aug-12 16:20 
QuestionHide the tab headers, not tab pages Pin
Zhi Chen11-Jul-12 7:48
Zhi Chen11-Jul-12 7:48 
AnswerRe: Hide the tab headers, not tab pages Pin
Zhi Chen11-Jul-12 11:00
Zhi Chen11-Jul-12 11:00 
QuestionA bug is exist on your control Pin
atrakonline7-Feb-12 22:42
atrakonline7-Feb-12 22:42 
GeneralC++/ActiveX version Pin
SergeyLozovoy21-Oct-09 23:20
SergeyLozovoy21-Oct-09 23:20 
GeneralLeft alignment, horizontal buttons Pin
Vin5525-Aug-09 8:40
Vin5525-Aug-09 8:40 
GeneralRe: Left alignment, horizontal buttons Pin
John Underhill25-Aug-09 13:31
John Underhill25-Aug-09 13:31 
GeneralThere is a bug in drawTabControl method Pin
Zhi Chen15-Jun-09 12:27
Zhi Chen15-Jun-09 12:27 
GeneralRe: There is a bug in drawTabControl method Pin
John Underhill17-Jun-09 16:17
John Underhill17-Jun-09 16:17 
GeneralIdea of improvement Pin
claudetom016-Jun-09 2:22
claudetom016-Jun-09 2:22 
GeneralVB-Version Pin
dherrmann2-Jun-09 6:12
dherrmann2-Jun-09 6:12 
GeneralRe: VB-Version Pin
Giotumba2-Jun-09 7:43
Giotumba2-Jun-09 7:43 
GeneralRe: VB-Version Pin
dherrmann2-Jun-09 7:55
dherrmann2-Jun-09 7:55 
GeneralRe: VB-Version Pin
Giotumba2-Jun-09 20:58
Giotumba2-Jun-09 20:58 
GeneralRe: VB-Version Pin
dherrmann2-Jun-09 21:25
dherrmann2-Jun-09 21:25 
GeneralRe: VB-Version Pin
Giotumba2-Jun-09 21:46
Giotumba2-Jun-09 21:46 
GeneralRe: VB-Version Pin
dherrmann2-Jun-09 23:06
dherrmann2-Jun-09 23:06 
GeneralRe: VB-Version Pin
John Underhill2-Jun-09 11:47
John Underhill2-Jun-09 11:47 
Generalgreat Pin
Huisheng Chen25-May-09 16:00
Huisheng Chen25-May-09 16:00 
GeneralRe: great Pin
John Underhill26-May-09 1:38
John Underhill26-May-09 1:38 

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.