Another bug that crept up in SP1 of the .Net 1.1 Framework is the nested group box caption problem. The bug is that captions of nested group boxes are rendered in bold and in the space of the caption as if it wasn't bold, thus cutting off the text. You've probably seen it in some application, if not here's a shot of the issue:

Microsoft does not currently have a fix for this problem, but there is a fairly simple solution to it. You can place a panel inside the main group box and place the nested group box inside the panel and dock it. This will make the problem go away. But I don't particularly like this solution, it involves messing around with too many controls on the UI. I was looking for a solution that involves sub-classing the group box control so I could just replace the type name in code.
Googling around I did not find much along these lines. One post I'd found had some simple code that did not seem to work. So I decided to do it myself. The idea is simple, just overwrite the caption using the right font. This basically involves clearing the background and redrawing the caption. Simple enough, just use FillRectangle and DrawString and it's all done.
public class AxoGroupBox : GroupBox
{
private const int WM_PAINT = 0x000F;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
// get graphics device
using (Graphics g = Graphics.FromHwnd(this.Handle))
{
if (g != null)
{
SizeF sz = g.MeasureString(this.Text, this.Font);
Rectangle bounds = new Rectangle(10, 0, (int)sz.Width, (int)sz.Height);
g.FillRectangle(new SolidBrush(this.BackColor), bounds);
g.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), bounds.Left, bounds.Top);
}
}
}
}
}
Not so fast! This works fine and dandy unless your application uses visual styles, which is the case with OnTime. This means we have to deal with the themes because group box captions are drawn in different colors. It turns out to be a little easier than it sounds.
There's a set of API functions in the uxtheme.dll that allow us to deal with XP themes. But first, we use the OSFeature class to find out if themes are supported on the current OS. Next we make sure the application is themed (i.e. the user could be not using themes). Then we start doing whatever we need to do for the theme. In my case I only care about getting the color of the group box caption and I'll use my previous code to re-draw it. If you want to get more involved, you should probably use the DrawThemeText function and all the other Draw functions to have the system do it for you. I decided not to go that far, just needed a simple solution which works in my case.
So here's my sub-classed group box control:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Axosoft.WinControls
{
public class AxoGroupBox : GroupBox
{
private const int WM_PAINT = 0x000F;
// part id
private const int BP_GROUPBOX = 4;
// state id
private const int PBS_NORMAL = 1;
// property id
private const int TMT_TEXTCOLOR = 3803;
[DllImport("uxtheme.dll", ExactSpelling=true, CharSet=CharSet.Unicode)]
public static extern IntPtr OpenThemeData(IntPtr hWnd, String classList);
[DllImport("uxtheme.dll", ExactSpelling=true)]
public extern static Int32 CloseThemeData(IntPtr hTheme);
[DllImport("uxtheme.dll", ExactSpelling=true)]
public extern static int IsAppThemed();
[DllImport("uxtheme.dll", ExactSpelling=true)]
public extern static Int32 GetThemeColor(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out UInt32 pColor);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
// get graphics device
using (Graphics g = Graphics.FromHwnd(this.Handle))
{
if (g != null)
{
Color groupText = this.ForeColor;
try
{
// check to see if OS supports themes
if (OSFeature.Feature.IsPresent(OSFeature.Themes))
{
// check to see if the app is using themes
if (IsAppThemed() == 1)
{
// open theme data
IntPtr td = OpenThemeData(this.Handle, "Button");
if (td != IntPtr.Zero)
{
// get text color
UInt32 clr = 0;
if (GetThemeColor(td, BP_GROUPBOX, PBS_NORMAL, TMT_TEXTCOLOR, out clr) == 0)
{
// color is backwards (BGR) => adjust value
int iclr = (int)clr;
groupText = Color.FromArgb(iclr & 0xff, (iclr & 0xff00) >> 8, (iclr & 0xff0000) >> 16);
}
// close theme data
CloseThemeData(td);
}
}
}
}
catch {}
SizeF sz = g.MeasureString(this.Text, this.Font);
Rectangle bounds = new Rectangle(10, 0, (int)sz.Width, (int)sz.Height);
g.FillRectangle(new SolidBrush(this.BackColor), bounds);
g.DrawString(this.Text, this.Font, new SolidBrush(groupText), bounds.Left, bounds.Top);
}
}
}
}
}
}
So here it is in action: