How can I draw a rounded rectangle as the border for a rounded Form?

A base implementation of what is described in the comments.
The Form frmRoundCorners provides some properties that allow to draw its rounded area with a custom BackColor, a custom BorderColor and a custom inner border color, acting as a shadow for the internal side of the Form’s border.

The Form itself is implemented using a base class, baseForm, derived from Form, so the Form’s properties can be set in the Form designer.

The transparency is activated setting the Form’s original BackColor equal to its TrasparencyKey, making its ClientArea completely transparent, but drawable.
The Form’s original border is set to FormBorderStyle.None in the base class constructor.
I didn’t set a specific BackColor/TransparencyKey Color (it must be set in the Form’s designer) because I think it’s something one need to experiment with. I’d suggest a medium gray color. Avoid red components.

The Form can be moved, clicking on any point of its ClientArea and dragging it.

The minimum/maximum curvature of the Form and its custom Border is set to 15 and 180 degrees. It cannot be changed to a different range using the PropertyGrid.
The rounded area of the Form and its border are drawn using the GraphicsPath.AddArc() method, then applying a Matrix transformation to the Graphics object, both in the Scale and the Transform (position) components. The Size component is untouched.

This is what it looks like:

Rounded draggable borderless Form

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

[ToolboxItem(false)]
public partial class frmRoundCorners : baseForm
{
    private GraphicsPath pathRegion = new GraphicsPath(FillMode.Winding);
    private GraphicsPath pathBorder;
    Point pMousePosition = Point.Empty;

    public frmRoundCorners()
    {
        SetStyle(ControlStyles.AllPaintingInWmPaint |
                 ControlStyles.UserPaint |
                 ControlStyles.OptimizedDoubleBuffer |
                 ControlStyles.ResizeRedraw, true);
        InitializeComponent();
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left) {
            pMousePosition = e.Location;
        }
        base.OnMouseDown(e);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left) {
            Point screenPos = PointToScreen(e.Location);
            this.Location = new Point(screenPos.X - pMousePosition.X, screenPos.Y - pMousePosition.Y);
        }
        base.OnMouseMove(e);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
        RoundedCornerRectangle(ClientRectangle);
        RectangleF rect = pathRegion.GetBounds();
        float scaleX = 1 - (BorderSize / rect.Width);
        float scaleY = 1 - (BorderSize / rect.Height);
        using (Pen pen = new Pen(BorderColor, BorderSize))
        using (Pen penBorder = new Pen(InternalBorderColor, 2))
        using (var brush = new SolidBrush(FillColor))
        using (var mx = new Matrix(scaleX, 0, 0, scaleY, (pen.Width / 2), (pen.Width / 2)))
        {
            e.Graphics.Transform = mx;
            e.Graphics.FillPath(brush, pathRegion);
            e.Graphics.DrawPath(penBorder, pathBorder);
            e.Graphics.DrawPath(pen, pathRegion);
        }
    }

    private void RoundedCornerRectangle(Rectangle r)
    {
        pathRegion = new GraphicsPath(FillMode.Alternate);
        float innerCurve = CurveAngle - m_PenSizeOffset;

        pathRegion.StartFigure();
        pathRegion.AddArc(r.X, r.Y, CurveAngle, CurveAngle, 180, 90);
        pathRegion.AddArc(r.Right - CurveAngle, r.Y, CurveAngle, CurveAngle, 270, 90);
        pathRegion.AddArc(r.Right - CurveAngle, r.Bottom - CurveAngle, CurveAngle, CurveAngle, 0, 90);
        pathRegion.AddArc(r.X, r.Bottom - CurveAngle, CurveAngle, CurveAngle, 90, 90);
        pathRegion.CloseFigure();

        pathBorder = new GraphicsPath();
        pathBorder.StartFigure();
        pathBorder.AddArc(r.X + m_PenSizeOffset, r.Y + m_PenSizeOffset, innerCurve, innerCurve, 180, 90);
        pathBorder.AddArc(r.Right - innerCurve - m_PenSizeOffset, r.Y + m_PenSizeOffset, innerCurve, innerCurve, 270, 90);
        pathBorder.AddArc(r.Right - innerCurve - m_PenSizeOffset, r.Bottom - innerCurve- m_PenSizeOffset, innerCurve, innerCurve, 0, 90);
        pathBorder.AddArc(r.X + m_PenSizeOffset, r.Bottom - innerCurve - m_PenSizeOffset, innerCurve, innerCurve, 90, 90);
        pathBorder.CloseFigure();
    }
}

baseForm class:

public class baseForm : Form
{
    private Color m_InternalBorderColor = Color.FromArgb(128, 128, 128);
    private Color m_BorderColor = Color.Red;
    private Color m_FillColor = Color.WhiteSmoke;
    private float m_PenSize = 6f;
    private float m_CurveAngle = 60.0f;
    internal float m_PenSizeOffset = 3f;

    public baseForm() => InitializeComponent();
    private void InitializeComponent() => FormBorderStyle = FormBorderStyle.None;

    [EditorBrowsable(EditorBrowsableState.Always), Browsable(true), Category("Appearance")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [DefaultValue(60.0f)]
    public virtual float CurveAngle
    {
        get => m_CurveAngle;
        set {
            m_CurveAngle = Math.Max(Math.Min(value, 180), 15);
            Invalidate();
        }
    }
    [EditorBrowsable(EditorBrowsableState.Always), Browsable(true), Category("Appearance")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [DefaultValue(6.0f)]
    public virtual float BorderSize
    {
        get => m_PenSize;
        set {
            m_PenSize = value;
            m_PenSizeOffset = value / 2.0f;
            Invalidate();
        }
    }

    [EditorBrowsable(EditorBrowsableState.Always), Browsable(true), Category("Appearance")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public virtual Color BorderColor
    {
        get => m_BorderColor;
        set {
            m_BorderColor = value;
            Invalidate();
        }
    }

    [EditorBrowsable(EditorBrowsableState.Always), Browsable(true), Category("Appearance")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public virtual Color FillColor
    {
        get => m_FillColor;
        set {
            m_FillColor = value;
            Invalidate();
        }
    }

    [EditorBrowsable(EditorBrowsableState.Always), Browsable(true), Category("Appearance")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [Description("Get or Set the Color of the internal border")]
    public virtual Color InternalBorderColor
    {
        get => m_InternalBorderColor;
        set {
            m_InternalBorderColor = value;
            Invalidate();
        }
    }

    [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [DefaultValue(FormBorderStyle.None)]
    public new FormBorderStyle FormBorderStyle
    {
        get => base.FormBorderStyle;
        set => base.FormBorderStyle = FormBorderStyle.None;
    }
}

Leave a Comment