I made something similar a while back and never did anything with it. I started again today as usual because the interest took me lol. So there are custom group boxes out there and I have seen a few on Code Project, but in this blog I have added a few different features simply by exposing properties of the Pen and Brush objects.
Examples of my control, these are in the source I have provided below in the link:
The object itself
All I have done to maintain functionality and simply extend is to inherit from the GroupBox, like below:
namespace WindowsForm_Examples_NET_2
{
public partial class RoundedGroupBox : GroupBox
{
Next I added some properties to it. This is the dead easy part, as all I am doing is providing exposure to the properties of the Pen and Brush as well as other things:
//This is the size of the text of the groupbox, e.g. _groupNameDimensions = e.Graphics.MeasureString(this.Text, this.Font);
private SizeF _groupNameDimensions;
//The radius of the corners with a default value of 10
private float _cornerRadius = 10;
//The leading text margin of the left side
private float _leadingTextMargin = 25;
// I need a graphics path here for the corner arcs and then finally close the figure
private GraphicsPath _roundBoxPath;
// The first colour of the gradient
private Color _gradientFrom = Color.Gray;
//The second colour of the gradient
private Color _gradientTo = Color.White;
//The angle of the gradient
private float _gradientAngle = 90;
//The fill type, this could be Fill or Linear Gradient
private FillType _groupFillType = FillType.Fill;
//The back colour. This is the colour within the Graphics path, if you change the controls back color that will be behind the groupbox
private Color _groupBackColor = Color.White;
//The colour of the border
private Color _groupBorderColor = Color.Black;
//The width of the border
private int _groupBorderWidth = 1;
//The dash style of the border, this can be set to Solid
private DashStyle _groupLineStyle = DashStyle.Solid;
//A Custom pattern for the dashes like 10,50 etc...
private float[] _dashPattern = new float[] { 5, 5 };
I encapsulate these fields so you will see the properties, I also attach the following attribute for design time purposes:
[NotifyParentProperty(true)]
public float[] DashPattern
{
get { return _dashPattern; }
set { _dashPattern = value;
}
}
I can also now attach some descriptions and even categories so that it will look tidy inside the Property Box. I have not added these at this time of writing, but for this one paste into the blog I will.
[Description("The custom pattern for the Group Box Border")]
[Category("GroupBox Dash")]
[NotifyParentProperty(true)]
public float[] DashPattern
{
get { return _dashPattern; }
set { _dashPattern = value;
}
}
This will give you the following behaviour inside the properties window:
Drawing the curved container
I have not gone into Maths yet as much as I should and want being a programmer so please excuse me if the route I have taken,is in any shape a long and inefficient way. The approach I took was to identify the key area where I would place an arc. After this I noted down the sweep angles. For the purposes of this control they will always sweep a distance of 90 degrees but the starting angle will differ.
With reference to the above image please find the method I wrote in order to draw the container. I feel I have over complicated the equations which enable the control to adapt to changes in size and also changes to the Font property of the Group Text.
private PointF p1;
private PointF p2;
private PointF p3;
private PointF p4;
private PointF p5;
private PointF p6;
private PointF p7;
private PointF p8;
protected override void OnPaint(PaintEventArgs e)
{
//This will give the lines a smoother look.
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
//I assign the size of the GroupBox Text property
_groupNameDimensions = e.Graphics.MeasureString(this.Text, this.Font);
//Below I create the coordinates for each point, I rake into account the control margin, the radius, the size of the text and also the leading Text margin.
p1 = new PointF(_leadingTextMargin > (_cornerRadius * 2) ? _leadingTextMargin : (_cornerRadius * 2), this.Margin.Top);
p2 = new PointF(p1.X + _cornerRadius + _groupNameDimensions.Width, this.Margin.Top);
p3 = new PointF(p1.X + (_cornerRadius * 2) + _groupNameDimensions.Width, this.Margin.Top + _cornerRadius + _groupNameDimensions.Height);
p4 = new PointF(this.Width - _cornerRadius - this.Margin.Right, this.Margin.Top + (_cornerRadius * 2) + _groupNameDimensions.Height);
p5 = new PointF(this.Width - _cornerRadius - this.Margin.Right, this.Height - _cornerRadius - this.Margin.Bottom);
p6 = new PointF(this.Margin.Left, this.Height - _cornerRadius - this.Margin.Bottom);
p7 = new PointF(this.Margin.Left, this.Margin.Top + (_cornerRadius * 2) + _groupNameDimensions.Height);
p8 = new PointF(_leadingTextMargin > (_cornerRadius * 2) ? _leadingTextMargin - _cornerRadius : _cornerRadius, this.Margin.Top + _cornerRadius + _groupNameDimensions.Height);
// I instantiate the Graphics Path
_roundBoxPath = new GraphicsPath();
//I now add the arcs to the graphics path using a helper function to genrate the required rectangle, bit overkill I will admit
_roundBoxPath.AddArc(GetRectangle(p1), 180F, 90F);
_roundBoxPath.AddArc(GetRectangle(p2), 270F, 90F);
_roundBoxPath.AddArc(GetRectangle(p3), 180F, -90F);
_roundBoxPath.AddArc(GetRectangle(p4), 270F, 90F);
_roundBoxPath.AddArc(GetRectangle(p5), 0F, 90F);
_roundBoxPath.AddArc(GetRectangle(p6), 90F, 90F);
_roundBoxPath.AddArc(GetRectangle(p7), 180F, 90F);
_roundBoxPath.AddArc(GetRectangle(p8), 90F, -90F);
_roundBoxPath.CloseFigure();
//I define a pen and conitionally assign the prroperties to it
Pen p = new Pen(GroupBorderColor);
p.DashStyle = GroupLineStyle;
p.Width = GroupBorderWidth;
if (GroupLineStyle == DashStyle.Custom)
{
p.DashPattern = DashPattern;
}
e.Graphics.DrawPath(p, _roundBoxPath);
//Here I conditionally assign the way the group box should be filled with colour
switch (this.GroupFillType)
{
case FillType.Fill:
e.Graphics.FillPath(new SolidBrush(GroupBackColor), _roundBoxPath);
break;
default:
LinearGradientBrush lgb = new LinearGradientBrush(new Rectangle(0,0,this.Width,this.Height), GradientFrom, GradientTo, GradientAngle);
e.Graphics.FillPath(lgb, _roundBoxPath);
break;
}
//Here I draw the group name text into the tab like shape.
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), new PointF(_leadingTextMargin > (_cornerRadius * 2) ? _leadingTextMargin + _cornerRadius : (_cornerRadius * 2) + _cornerRadius, this.Margin.Top + (_cornerRadius / 2)));
//To avoid being inconsitent I set the minimum with every time it invalidates so as to keep the tab in propeortion to the groiup box.
this.MinimumSize = new Size(Convert.ToInt32(_leadingTextMargin + _groupNameDimensions.Width +(_cornerRadius*3)), 0);
}
private RectangleF GetRectangle(PointF p)
{
return new RectangleF(p, new SizeF(_cornerRadius, _cornerRadius));
}
Transparency
Even though I have inherited from the groupbox control I can still make this transparent, by setting some of the styles of the form, the crucial one being SupportTransparentBackColor. To avoid flickering as well when you resize the control I have set the ResizeRedraw flag.
public RoundedGroupBox()
{
InitializeComponent();
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.SetStyle(ControlStyles.ContainerControl, true);
this.BackColor = Color.Transparent;
this.Width = 175;
this.Height = 75;
}
The Source Code
I have not yet figured out how to upload a file with Windows Live Writer yet so I have placed this file on my server. Thanks for reading and hope it is of some interest.
http://andrewrea.co.uk/RoundedCornerGroupBox/RoundedCornerGroupBox.rar