Ian Barber
Ian Barber

Reputation: 173

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

Upvotes: 1

Views: 818

Answers (3)

F. Felisarta
F. Felisarta

Reputation: 1

I tried Jimi's code and successfully run it. However, when the said groupbox Text is empty, it will have a small gap (the border is not totally closed). I'm no expert with VB.Net, and find it difficult to test the code.

Fortunately, using the original code as posted by Ian Barber, with trial and errors it was solved by adding a constant value to the text size width (textRect.Width = tSize.Width + 2).

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    Dim tSize As SizeF = e.Graphics.MeasureString(Me.Text, Me.Font) '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 + 2
    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

Upvotes: 0

Jimi
Jimi

Reputation: 32223

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

Upvotes: 2

keco
keco

Reputation: 146

Do not mix TextRenderer methods and Graphics methods for string measurement and drawing. When you have a graphics object, you should use it.

Use Graphics.MeasureString() and Graphics.DrawString() or TextRenderer.MeasureText() and TextRenderer.DrawText().

Upvotes: 2

Related Questions