Search code examples
.netvb.netwinformsgraphicsgroupbox

Custom GroupBox Control with colored border


I have found this Custom Control, derived from GroupBox, that allows to change the color of its border.
I understand that the code originally came from StackOverflow, although I cannot find it.

For some reason, when setting the Text Property of the GroupBox, the last letter is always cut out.
Can anyone with more experience than me see anything in the code which is causing this?

Public Class myGroupBox
    Inherits GroupBox

    Private borderColor As Color

    Public Sub New()
        MyBase.New
        Me.borderColor = Color.Blue
    End Sub

    Public Property BorderColour() As Color
        Get
            Return Me.borderColor
        End Get
        Set(ByVal value As Color)
            Me.borderColor = value
        End Set
    End Property

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim tSize As Size = TextRenderer.MeasureText(Me.Text, Me.Font)
        Dim borderRect As Rectangle = e.ClipRectangle
        borderRect.Y = (borderRect.Y + (tSize.Height / 2))
        borderRect.Height = (borderRect.Height - (tSize.Height / 2))
        ControlPaint.DrawBorder(e.Graphics, borderRect, Me.borderColor, ButtonBorderStyle.Solid)
        Dim textRect As Rectangle = e.ClipRectangle
        textRect.X = (textRect.X + 6)
        textRect.Width = tSize.Width
        textRect.Height = tSize.Height
        e.Graphics.FillRectangle(New SolidBrush(Me.BackColor), textRect)
        e.Graphics.DrawString(Me.Text, Me.Font, New SolidBrush(Me.ForeColor), textRect)
    End Sub
End Class

Solution

  • There are some problems with that code:

    • The text is measured using TextRenderer.MeasureText, but the current Graphics (IdeviceContext) object is not passed to method.
    • It uses e.ClipRectangle as the measure of the Border: better use Control.ClientRectangle
    • Graphics.DrawString is used to draw the Text, when another tool was used to measure it
    • The Border Color is hard-coded, so it cannot be changed using the PropertyGrid
    • It was built with Option Strict Off

    ► To note: this is not how the Framework draws the borders of a GroupBox. We should draw lines instead, otherwise the text cannot be rendered transparent: since it's the drawn text that hides the line drawn by ControlPaint.DrawBorder, the text background cannot be transparent.

    Here's a revisited version of that Control, with some adjustments that may be useful in other occasions:
    If you think that the Text is drawn too close to the left side, just offset it as required. You could also add a Property to defined the alignment.

    • When you first drop the GroupBox in a Form, the border's Color is set to SystemColors.Window: use the PropertyGrid to set another Color.

    Public Class myGroupBox
        Inherits GroupBox
    
        Private ReadOnly flags As TextFormatFlags =
            TextFormatFlags.Top Or TextFormatFlags.Left Or
            TextFormatFlags.LeftAndRightPadding Or TextFormatFlags.EndEllipsis
        Private m_BorderColor As Color = SystemColors.Window
    
        Public Property BorderColor As Color
            Get
                Return m_BorderColor
            End Get
            Set
                m_BorderColor = Value
                Me.Invalidate()
                If DesignMode Then Me.Parent?.Invalidate(Me.Bounds)
            End Set
        End Property
    
        Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
            Dim midText = TextRenderer.MeasureText(e.Graphics, Text, Font, ClientSize).Height \ 2 + 2
            Dim rect = New Rectangle(0, midText, ClientSize.Width, ClientSize.Height - midText)
            ControlPaint.DrawBorder(e.Graphics, rect, BorderColor, ButtonBorderStyle.Solid)
    
            Dim textRect = Rectangle.Inflate(ClientRectangle, -4, 0)
            TextRenderer.DrawText(e.Graphics, $" {Me.Text} ", Font, textRect, ForeColor, BackColor, flags)
        End Sub
    End Class