37

In C#.NET I am trying to programmatically change the color of the border in a group box.

Update: This question was asked when I was working on a winforms system before we switched to .NET.

Amy Patterson
  • 679
  • 1
  • 10
  • 24
  • @Amy Check _user1944617_ answer, it is really nice and superior imho to accepted one. just tried and fit perfectly with default group box design. – Drake Mar 05 '14 at 11:17

7 Answers7

45

Just add paint event.

    private void groupBox1_Paint(object sender, PaintEventArgs e)
    {
        GroupBox box = sender as GroupBox;
        DrawGroupBox(box, e.Graphics, Color.Red, Color.Blue);
    }


    private void DrawGroupBox(GroupBox box, Graphics g, Color textColor, Color borderColor)
    {
        if (box != null)
        {
            Brush textBrush = new SolidBrush(textColor);
            Brush borderBrush = new SolidBrush(borderColor);
            Pen borderPen = new Pen(borderBrush);
            SizeF strSize = g.MeasureString(box.Text, box.Font);
            Rectangle rect = new Rectangle(box.ClientRectangle.X,
                                           box.ClientRectangle.Y + (int)(strSize.Height / 2),
                                           box.ClientRectangle.Width - 1,
                                           box.ClientRectangle.Height - (int)(strSize.Height / 2) - 1);

            // Clear text and border
            g.Clear(this.BackColor);

            // Draw text
            g.DrawString(box.Text, box.Font, textBrush, box.Padding.Left, 0);

            // Drawing Border
            //Left
            g.DrawLine(borderPen, rect.Location, new Point(rect.X, rect.Y + rect.Height));
            //Right
            g.DrawLine(borderPen, new Point(rect.X + rect.Width, rect.Y), new Point(rect.X + rect.Width, rect.Y + rect.Height));
            //Bottom
            g.DrawLine(borderPen, new Point(rect.X, rect.Y + rect.Height), new Point(rect.X + rect.Width, rect.Y + rect.Height));
            //Top1
            g.DrawLine(borderPen, new Point(rect.X, rect.Y), new Point(rect.X + box.Padding.Left, rect.Y));
            //Top2
            g.DrawLine(borderPen, new Point(rect.X + box.Padding.Left + (int)(strSize.Width), rect.Y), new Point(rect.X + rect.Width, rect.Y));
        }
    }
user1944617
  • 471
  • 4
  • 3
26

Building on the previous answer, a better solution that includes the label for the group box:

groupBox1.Paint += PaintBorderlessGroupBox;

private void PaintBorderlessGroupBox(object sender, PaintEventArgs p)
{
  GroupBox box = (GroupBox)sender;
  p.Graphics.Clear(SystemColors.Control);
  p.Graphics.DrawString(box.Text, box.Font, Brushes.Black, 0, 0);
}

You might want to adjust the x/y for the text, but for my use this is just right.

Oliver
  • 43,366
  • 8
  • 94
  • 151
Mick Bruno
  • 1,373
  • 15
  • 13
6

FWIW, this is the implementation I used. It's a child of GroupBox but allows setting not only the BorderColor, but also the thickness of the border and the radius of the rounded corners. Also, you can set the amount of indent you want for the GroupBox label, and using a negative indent indents from the right side.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace BorderedGroupBox
{
    public class BorderedGroupBox : GroupBox
    {
        private Color _borderColor = Color.Black;
        private int _borderWidth = 2;
        private int _borderRadius = 5;
        private int _textIndent = 10;

        public BorderedGroupBox() : base()
        {
            InitializeComponent();
            this.Paint += this.BorderedGroupBox_Paint;
        }

        public BorderedGroupBox(int width, float radius, Color color) : base()
        {
            this._borderWidth = Math.Max(1,width);
            this._borderColor = color;
            this._borderRadius = Math.Max(0,radius);
            InitializeComponent();
            this.Paint += this.BorderedGroupBox_Paint;
        }

        public Color BorderColor
        {
            get => this._borderColor;
            set
            {
                this._borderColor = value;
                DrawGroupBox();
            }
        }

        public int BorderWidth
        {
            get => this._borderWidth;
            set
            {
                if (value > 0)
                {
                    this._borderWidth = Math.Min(value, 10);
                    DrawGroupBox();
                }
            }
        }

        public int BorderRadius
        {
            get => this._borderRadius;
            set
            {   // Setting a radius of 0 produces square corners...
                if (value >= 0)
                {
                    this._borderRadius = value;
                    this.DrawGroupBox();
                }
            }
        }

        public int LabelIndent
        {
            get => this._textIndent;
            set
            {
                this._textIndent = value;
                this.DrawGroupBox();
            }
        }

        private void BorderedGroupBox_Paint(object sender, PaintEventArgs e) =>
            DrawGroupBox(e.Graphics);

        private void DrawGroupBox() =>
            this.DrawGroupBox(this.CreateGraphics());

        private void DrawGroupBox(Graphics g)
        {
            Brush textBrush = new SolidBrush(this.ForeColor);
            SizeF strSize = g.MeasureString(this.Text, this.Font);

            Brush borderBrush = new SolidBrush(this.BorderColor);
            Pen borderPen = new Pen(borderBrush,(float)this._borderWidth);
            Rectangle rect = new Rectangle(this.ClientRectangle.X,
                                            this.ClientRectangle.Y + (int)(strSize.Height / 2),
                                            this.ClientRectangle.Width - 1,
                                            this.ClientRectangle.Height - (int)(strSize.Height / 2) - 1);

            Brush labelBrush = new SolidBrush(this.BackColor);

            // Clear text and border
            g.Clear(this.BackColor);

            // Drawing Border (added "Fix" from Jim Fell, Oct 6, '18)
            int rectX = (0 == this._borderWidth % 2) ? rect.X + this._borderWidth / 2 : rect.X + 1 + this._borderWidth / 2;
            int rectHeight = (0 == this._borderWidth % 2) ? rect.Height - this._borderWidth / 2 : rect.Height - 1 - this._borderWidth / 2;
            // NOTE DIFFERENCE: rectX vs rect.X and rectHeight vs rect.Height
            g.DrawRoundedRectangle(borderPen, rectX, rect.Y, rect.Width, rectHeight, (float)this._borderRadius);

            // Draw text
            if (this.Text.Length > 0)
            {
                // Do some work to ensure we don't put the label outside
                // of the box, regardless of what value is assigned to the Indent:
                int width = (int)rect.Width, posX;
                posX = (this._textIndent < 0) ? Math.Max(0-width,this._textIndent) : Math.Min(width, this._textIndent);
                posX = (posX < 0) ? rect.Width + posX - (int)strSize.Width : posX;
                g.FillRectangle(labelBrush, posX, 0, strSize.Width, strSize.Height);
                g.DrawString(this.Text, this.Font, textBrush, posX, 0);
            }
        }

        #region Component Designer generated code
        /// <summary>Required designer variable.</summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>Clean up any resources being used.</summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();

            base.Dispose(disposing);
        }

        /// <summary>Required method for Designer support - Don't modify!</summary>
        private void InitializeComponent() => components = new System.ComponentModel.Container();
        #endregion
    }
}

To make it work, you also have to extend the base Graphics class (Note: this is derived from some code I found on here once when I was trying to create a rounded-corners Panel control, but I can't find the original post to link here):

static class GraphicsExtension
{
    private static GraphicsPath GenerateRoundedRectangle(
        this Graphics graphics,
        RectangleF rectangle,
        float radius)
    {
        float diameter;
        GraphicsPath path = new GraphicsPath();
        if (radius <= 0.0F)
        {
            path.AddRectangle(rectangle);
            path.CloseFigure();
            return path;
        }
        else
        {
            if (radius >= (Math.Min(rectangle.Width, rectangle.Height)) / 2.0)
                return graphics.GenerateCapsule(rectangle);
            diameter = radius * 2.0F;
            SizeF sizeF = new SizeF(diameter, diameter);
            RectangleF arc = new RectangleF(rectangle.Location, sizeF);
            path.AddArc(arc, 180, 90);
            arc.X = rectangle.Right - diameter;
            path.AddArc(arc, 270, 90);
            arc.Y = rectangle.Bottom - diameter;
            path.AddArc(arc, 0, 90);
            arc.X = rectangle.Left;
            path.AddArc(arc, 90, 90);
            path.CloseFigure();
        }
        return path;
    }

    private static GraphicsPath GenerateCapsule(
        this Graphics graphics,
        RectangleF baseRect)
    {
        float diameter;
        RectangleF arc;
        GraphicsPath path = new GraphicsPath();
        try
        {
            if (baseRect.Width > baseRect.Height)
            {
                diameter = baseRect.Height;
                SizeF sizeF = new SizeF(diameter, diameter);
                arc = new RectangleF(baseRect.Location, sizeF);
                path.AddArc(arc, 90, 180);
                arc.X = baseRect.Right - diameter;
                path.AddArc(arc, 270, 180);
            }
            else if (baseRect.Width < baseRect.Height)
            {
                diameter = baseRect.Width;
                SizeF sizeF = new SizeF(diameter, diameter);
                arc = new RectangleF(baseRect.Location, sizeF);
                path.AddArc(arc, 180, 180);
                arc.Y = baseRect.Bottom - diameter;
                path.AddArc(arc, 0, 180);
            }
            else path.AddEllipse(baseRect);
        }
        catch { path.AddEllipse(baseRect); }
        finally { path.CloseFigure(); }
        return path;
    }

    /// <summary>
    /// Draws a rounded rectangle specified by a pair of coordinates, a width, a height and the radius
    /// for the arcs that make the rounded edges.
    /// </summary>
    /// <param name="brush">System.Drawing.Pen that determines the color, width and style of the rectangle.</param>
    /// <param name="x">The x-coordinate of the upper-left corner of the rectangle to draw.</param>
    /// <param name="y">The y-coordinate of the upper-left corner of the rectangle to draw.</param>
    /// <param name="width">Width of the rectangle to draw.</param>
    /// <param name="height">Height of the rectangle to draw.</param>
    /// <param name="radius">The radius of the arc used for the rounded edges.</param>
    public static void DrawRoundedRectangle(
        this Graphics graphics,
        Pen pen,
        float x,
        float y,
        float width,
        float height,
        float radius)
    {
        RectangleF rectangle = new RectangleF(x, y, width, height);
        GraphicsPath path = graphics.GenerateRoundedRectangle(rectangle, radius);
        SmoothingMode old = graphics.SmoothingMode;
        graphics.SmoothingMode = SmoothingMode.AntiAlias;
        graphics.DrawPath(pen, path);
        graphics.SmoothingMode = old;
    }

    /// <summary>
    /// Draws a rounded rectangle specified by a pair of coordinates, a width, a height and the radius
    /// for the arcs that make the rounded edges.
    /// </summary>
    /// <param name="brush">System.Drawing.Pen that determines the color, width and style of the rectangle.</param>
    /// <param name="x">The x-coordinate of the upper-left corner of the rectangle to draw.</param>
    /// <param name="y">The y-coordinate of the upper-left corner of the rectangle to draw.</param>
    /// <param name="width">Width of the rectangle to draw.</param>
    /// <param name="height">Height of the rectangle to draw.</param>
    /// <param name="radius">The radius of the arc used for the rounded edges.</param>

    public static void DrawRoundedRectangle(
        this Graphics graphics,
        Pen pen,
        int x,
        int y,
        int width,
        int height,
        int radius)
    {
        graphics.DrawRoundedRectangle(
            pen,
            Convert.ToSingle(x),
            Convert.ToSingle(y),
            Convert.ToSingle(width),
            Convert.ToSingle(height),
            Convert.ToSingle(radius));
    }
}
NetXpert
  • 511
  • 5
  • 14
  • 1
    two questions: 1) i gather i can only use BorderedGroupBox for programmatically-built forms; i.e. not something built with the IDE; and 2) what is graphics.GenerateCapsule()? – 4mla1fn Aug 08 '18 at 12:59
  • 1
    1) I use these controls in Visual Studio's designer without any problems... 2) lol -- I don't think I've ever used that much radius, didn't see the reference so didn't copy the code for it (woops!): – NetXpert Aug 09 '18 at 04:38
  • thanks brett. for other newbies, i found this link which explained how to create a custom control: http://www.blackwasp.co.uk/vscontrolintoolbox.aspx . quick and easy process. i'll enjoy using this control. – 4mla1fn Aug 10 '18 at 00:51
  • 1
    Fixed for drawing wide borders... `g.DrawRoundedRectangle(borderPen, (0 == this._borderWidth % 2) ? rect.X + this._borderWidth / 2 : rect.X + 1 + this._borderWidth / 2, rect.Y, rect.Width - this._borderWidth, (0 == this._borderWidth % 2) ? rect.Height - this._borderWidth / 2 : rect.Height - 1 - this._borderWidth / 2, (float)this._borderRadius);` – Jim Fell Oct 02 '18 at 15:10
  • 1
    @JimFell -- thanks! I've applied your suggestion to the code above (with attribution!). :) – NetXpert Oct 06 '18 at 15:57
  • I added a tweak to Jim Fell's code that worked a little better for me, it was too long to add as a comment so I have posted it below – compound eye Aug 07 '21 at 10:31
5

Just set the paint action on any object (not just buttons) to this method to draw a border.

    private void UserControl1_Paint(object sender, PaintEventArgs e)
    {
        ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);

    }

It still wont be pretty and rounded like the original, but it is much simpler.

Andy
  • 59
  • 1
  • 1
  • This DOESN'T achieve what's requested in the OP. It draws a new border around the existing groupbox – stigzler Jul 17 '23 at 20:17
1

I have achieved same border with something which might be simpler to understand for newbies:

    private void groupSchitaCentru_Paint(object sender, PaintEventArgs e)
    {
        Pen blackPen = new Pen(Color.Black, 2);
        Point pointTopLeft = new Point(0, 7);
        Point pointBottomLeft = new Point(0, groupSchitaCentru.ClientRectangle.Height);
        Point pointTopRight = new Point(groupSchitaCentru.ClientRectangle.Width, 7);
        Point pointBottomRight = new Point(groupSchitaCentru.ClientRectangle.Width, groupSchitaCentru.ClientRectangle.Height);

        e.Graphics.DrawLine(blackPen, pointTopLeft, pointBottomLeft);
        e.Graphics.DrawLine(blackPen, pointTopLeft, pointTopRight);
        e.Graphics.DrawLine(blackPen, pointBottomRight, pointTopRight);
        e.Graphics.DrawLine(blackPen, pointBottomLeft, pointBottomRight);
    }
  1. Set the Paint event on the GroupBox control. In this example the name of my control is "groupSchitaCentru". One needs this event because of its parameter e.
  2. Set up a pen object by making use of the System.Drawing.Pen class : https://msdn.microsoft.com/en-us/library/f956fzw1(v=vs.110).aspx
  3. Set the points which represent the corners of the rectangle represented by the control. Used the property ClientRectangle of the the control to get its dimensions. I used for TopLeft (0,7) because I want to respect the borders of the control, and draw the line about the its text. To get more information about the coordinates system walk here : https://learn.microsoft.com/en-us/dotnet/framework/winforms/windows-forms-coordinates

I do not know, may be it helps someone looking to achieve this border adjustment thing.

George
  • 653
  • 6
  • 18
  • Added `GroupBox groupBox = (GroupBox)sender;` so that same paint event can be used to redraw borders on all group boxes the same. – Jim Fell Oct 02 '18 at 15:05
1

I'm not sure this applies to every case, but thanks to this thread, we quickly hooked into the Paint event programmatically using:

GroupBox box = new GroupBox();
[...]
box.Paint += delegate(object o, PaintEventArgs p)
{
    p.Graphics.Clear(someColorHere);
};

Cheers!

swajak
  • 2,669
  • 3
  • 20
  • 22
0

This tweak to Jim Fell's code placed the borders a little better for me, but it's too long to add as a comment

...

   Rectangle rect = new Rectangle(this.ClientRectangle.X,
                                        this.ClientRectangle.Y + (int)(strSize.Height / 2),
                                        this.ClientRectangle.Width,
                                        this.ClientRectangle.Height - (int)(strSize.Height / 2));

    Brush labelBrush = new SolidBrush(this.BackColor);

    // Clear text and border
    g.Clear(this.BackColor);


    int drawX = rect.X;
    int drawY = rect.Y;
    int drawWidth = rect.Width;
    int drawHeight = rect.Height;

    if (this._borderWidth > 0)
    {
        drawX += this._borderWidth / 2;
        drawY += this._borderWidth / 2;

        drawWidth -= this._borderWidth;
        drawHeight -= this._borderWidth;
        
        if (this._borderWidth % 2 == 0)
        {
            drawX -= 1;
            drawWidth += 1;

            drawY -= 1;
            drawHeight += 1;
        }
    }

    g.DrawRoundedRectangle(borderPen, drawX, drawY, drawWidth, drawHeight, (float)this._borderRadius);
compound eye
  • 1,898
  • 18
  • 23