C# Groupbox – customising the look and feel of border / caption

ccaptiongroupbox

I have 2 groupboxes which I would like to customise a bit more, and I dont want to resort to having a panel with a label (this would mean that I would have to have the same background colour for the panel and the parent control if I wanted a border, since the label would have to have a colour set to cover up the border behind the text).

I have managed to change the border colour by capturing the paint event and using the following code:

Graphics gfx = e.Graphics;
Pen p = new Pen(Color.FromArgb(86, 136, 186), 3);

GroupBox gb = (GroupBox)sender;
Rectangle r = new Rectangle(0, 0, gb.Width, gb.Height);

gfx.DrawLine(p, 0, 5, 0, r.Height - 2);
gfx.DrawLine(p, 0, 5, 10, 5);
gfx.DrawLine(p, 62, 5, r.Width - 2, 5);
gfx.DrawLine(p, r.Width - 2, 5, r.Width - 2, r.Height - 2);
gfx.DrawLine(p, r.Width - 2, r.Height - 2, 0, r.Height - 2);

My problem is that, like this, if the caption is too long then it overlaps the border. As it is it overlaps the left hand border at the top – thats easy to solve simply by adjusting the 2nd DrawLine line. However I would like to detect the x and width measurements of the text so that I can position the borders properly.

Does anyone have any idea how to do this? I have looked on Google for a while but nothing jumps out at me. I know the caption is set through GroupBox.Text.

Please also say if there are any other measurements I may need, on the basis that I am changing the border thickness too so it would look odd if the font was tiny but the border was 10 pixels starting half way down…

Thanks in advance.

Regards,

Richard

Best Answer

It's easy to get the size of the string, as I see you've found out. But I think that subclassing the control would be much easier, allow for a better look and give you design time support. Here is an example:

public class GroupBoxEx : GroupBox
{
    SizeF sizeOfText;
    protected override void OnTextChanged(EventArgs e)
    {
        base.OnTextChanged(e);
        CalculateTextSize();            
    }

    protected override void OnFontChanged(EventArgs e)
    {
        base.OnFontChanged(e);
        CalculateTextSize();
    }

    protected void CalculateTextSize()
    {
        // measure the string:
        using (Graphics g = this.CreateGraphics())
        {
            sizeOfText = g.MeasureString(Text, Font);
        }
        linePen = new Pen(Color.FromArgb(86, 136, 186), sizeOfText.Height * 0.1F);
    }

    Pen linePen;

    protected override void OnPaint(PaintEventArgs e)
    {
        // Draw the string, we now have complete control over where:

        Rectangle r = new Rectangle(ClientRectangle.Left + Margin.Left, 
            ClientRectangle.Top + Margin.Top, 
            ClientRectangle.Width - Margin.Left - Margin.Right, 
            ClientRectangle.Height - Margin.Top - Margin.Bottom);

        const int gapInLine = 2;
        const int textMarginLeft = 7, textMarginTop = 2;

        // Top line:
        e.Graphics.DrawLine(linePen, r.Left, r.Top, r.Left + textMarginLeft - gapInLine, r.Top);
        e.Graphics.DrawLine(linePen, r.Left + textMarginLeft + sizeOfText.Width, r.Top, r.Right, r.Top);
        // and so on...

        // Now, draw the string at the desired location:            
        e.Graphics.DrawString(Text, Font, Brushes.Black, new Point(this.ClientRectangle.Left + textMarginLeft, this.ClientRectangle.Top - textMarginTop));
    }
}

You'll notice that the control doesn't paint itself anymore, you're in charge of the whole process. This allows you to know exactly where the text gets drawn - you're drawing it yourself.

(Note also that the line is 1/10 of the height of the string.)