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
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.
PAINTSTRUCT pntStrct = new PAINTSTRUCT();
_bPainting = true;
BeginPaint(m.HWnd, ref pntStrct);
ValidateRect(m.HWnd, ref pntStrct.rcPaint);
EndPaint(m.HWnd, ref pntStrct);
_bPainting = false;
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?
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.
if (align == TabAlignment.Bottom || align == TabAlignment.Top)
bm = new Bitmap(tabRect.Right - tabRect.Left, xsize);
bm = new Bitmap(xsize, tabRect.Right - tabRect.Left);
Graphics gcl = Graphics.FromImage(bm);
cl = _tabHeaderBitmap.Clone(new Rectangle((state * width) + 2, 2,
width - 4, _tabHeaderBitmap.Height - 2),
if (align == TabAlignment.Bottom || align == TabAlignment.Top)
gcl.DrawImage(cl, new Rectangle(2, 2, tabRect.Right - tabRect.Left, xsize));
gcl.DrawImage(cl, new Rectangle(2, 2, xsize, tabRect.Right - tabRect.Left));
cl = _tabHeaderBitmap.Clone(new Rectangle(state * width, 0, 2,
gcl.DrawImage(cl, new Rectangle(0, 0, 2, xsize));
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 = _tabHeaderBitmap.Clone(new Rectangle((state * width) +
(width - 2), 0, 2, _tabHeaderBitmap.Height),
gcl.DrawImage(cl, new Rectangle(bm.Width - 2, 0, 2, xsize));
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.
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:
Included are two examples of using a bitmap, an Internet Explorer 7, and a Vista like look:
I also included a Vista style and a gradient in the custom drawn example.
The class also includes an upgrade to the ToolTip with an internal tip class.
- 25th May, 2009: Initial post