Crear Formulario de Bordes Redondeados Suaves – C#, VB & WinForms

Hola, en esta ocasión les enseñaré cómo crear un formulario con esquinas redondeadas suaves en C#, Visual Basic y Windows Forms. Bueno, esto es casi imposible en Windows Form, como muchos saben, aplicar solo una región redondeada al formulario hará que las esquinas se vean pixeladas o entrecortadas, en un control se puede solucionar fácilmente dibujando un borde exterior con el mismo color del formulario o control contenedor, como les mostré en los tutoriales anteriores de controles personalizados. Sin embargo, eso me dio la idea de hacer lo mismo con un formulario, es decir, obtener el color de fondo posterior al formulario y dibujar un borde exterior, pero el fondo posterior al formulario no tendrá un solo color, como se muestra en mi escritorio, es una imagen de muchos colores, por lo que será necesario obtener colores de al menos las 4 esquinas del formulario para que se mezcle correctamente con el fondo y así aparentar unas esquinas redondeadas lisas. Sin embargo, para un mejor resultado, recomiendo obtener más colores de los puntos clave de los límites del formulario y con ello dibujar un borde degradado.

Campos & Constructor

    public partial class Form1 : Form
    {
        //Fields
        private int borderRadius = 20;
        private int borderSize = 2;
        private Color borderColor = Color.FromArgb(128, 128, 255);

        //Constructor
        public Form1()
        {
            InitializeComponent();
            this.FormBorderStyle = FormBorderStyle.None;
            this.Padding = new Padding(borderSize);
            this.panelTitleBar.BackColor = borderColor;
            this.BackColor = borderColor;
        }
Public Class Form1

    'Fields
    Private borderRadius As Integer = 30
    Private borderSize As Integer = 3
    Private borderColor As Color = Color.HotPink

    'Constructor
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call.
        Me.FormBorderStyle = FormBorderStyle.None
        Me.Padding = New Padding(borderSize)
        Me.PanelTitleBar.BackColor = borderColor
        Me.BackColor = borderColor
    End Sub

Método Mover/Arrastrar Formulario

    //Drag Form
    [DllImport("user32.DLL", EntryPoint = "ReleaseCapture")]
    private extern static void ReleaseCapture();
    [DllImport("user32.DLL", EntryPoint = "SendMessage")]
    private extern static void SendMessage(System.IntPtr hWnd, int wMsg, int wParam, int lParam);        
    private void panelTitleBar_MouseDown(object sender, MouseEventArgs e)
    {
        ReleaseCapture();
        SendMessage(this.Handle, 0x112, 0xf012, 0);
    }
    'Drag Form
    <DllImport("user32.DLL", EntryPoint:="ReleaseCapture")>
    Private Shared Sub ReleaseCapture()
    End Sub
    <DllImport("user32.DLL", EntryPoint:="SendMessage")>
    Private Shared Sub SendMessage(ByVal hWnd As System.IntPtr, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)
    End Sub

    Private Sub panelTitleBar_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles MyBase.MouseDown, PanelTitleBar.MouseDown
        ReleaseCapture()
        SendMessage(Me.Handle, &H112, &HF012, 0)
    End Sub

Minimizar Formulario sin Bordes desde la Barra de tareas

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.Style |= 0x20000; // <--- Minimize borderless form from taskbar
            return cp;
        }
    }
    'Minimize borderless form from taskbar
    Protected Overrides ReadOnly Property CreateParams As CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            cp.Style = cp.Style Or &H20000 '<--- Minimize borderless form from taskbar
            Return cp
        End Get
    End Property

Métodos Privados

Crear Ruta Redondeada

    private GraphicsPath GetRoundedPath(Rectangle rect, float radius)
    {
        GraphicsPath path = new GraphicsPath();
        float curveSize = radius * 2F;

        path.StartFigure();
        path.AddArc(rect.X, rect.Y, curveSize, curveSize, 180, 90);
        path.AddArc(rect.Right - curveSize, rect.Y, curveSize, curveSize, 270, 90);
        path.AddArc(rect.Right - curveSize, rect.Bottom - curveSize, curveSize, curveSize, 0, 90);
        path.AddArc(rect.X, rect.Bottom - curveSize, curveSize, curveSize, 90, 90);
        path.CloseFigure();
        return path;
    }
    Private Function GetRoundedPath(rect As Rectangle, radius As Single) As GraphicsPath
        Dim path As GraphicsPath = New GraphicsPath()
        Dim curveSize As Single = radius * 2.0F
        path.StartFigure()
        path.AddArc(rect.X, rect.Y, curveSize, curveSize, 180, 90)
        path.AddArc(rect.Right - curveSize, rect.Y, curveSize, curveSize, 270, 90)
        path.AddArc(rect.Right - curveSize, rect.Bottom - curveSize, curveSize, curveSize, 0, 90)
        path.AddArc(rect.X, rect.Bottom - curveSize, curveSize, curveSize, 90, 90)
        path.CloseFigure()
        Return path
    End Function

Establecer Región y Borde Redondeado – Formulario

    private void FormRegionAndBorder(Form form, float radius, Graphics graph, Color borderColor, float borderSize)
    {
        if (this.WindowState != FormWindowState.Minimized)
        {
            using (GraphicsPath roundPath = GetRoundedPath(form.ClientRectangle, radius))
            using (Pen penBorder = new Pen(borderColor, borderSize))
            using (Matrix transform = new Matrix())
            {
                graph.SmoothingMode = SmoothingMode.AntiAlias;
                form.Region = new Region(roundPath);
                if (borderSize >= 1)
                {
                    Rectangle rect = form.ClientRectangle;
                    float scaleX = 1.0F - ((borderSize + 1) / rect.Width);
                    float scaleY = 1.0F - ((borderSize + 1) / rect.Height);

                    transform.Scale(scaleX, scaleY);
                    transform.Translate(borderSize / 1.6F, borderSize / 1.6F);

                    graph.Transform = transform;
                    graph.DrawPath(penBorder, roundPath);
                }
            }
        }
    }
    Private Sub FormRegionAndBorder(form As Form, radius As Single, graph As Graphics, borderColor As Color, borderSize As Single)
        If Me.WindowState <> FormWindowState.Minimized Then
            Using roundPath As GraphicsPath = GetRoundedPath(form.ClientRectangle, radius)
                Using penBorder As Pen = New Pen(borderColor, borderSize)
                    Using transform As Matrix = New Matrix()

                        graph.SmoothingMode = SmoothingMode.AntiAlias
                        form.Region = New Region(roundPath)
                        If borderSize >= 1 Then
                            Dim rect As Rectangle = form.ClientRectangle
                            Dim scaleX As Single = 1.0F - ((borderSize + 1) / rect.Width)
                            Dim scaleY As Single = 1.0F - ((borderSize + 1) / rect.Height)
                            transform.Scale(scaleX, scaleY)
                            transform.Translate(borderSize / 1.6F, borderSize / 1.6F)
                            graph.Transform = transform
                            graph.DrawPath(penBorder, roundPath)
                        End If

                    End Using
                End Using
            End Using
        End If
    End Sub

Establecer Región y Borde Redondeado – Panel Contenedor

    private void ControlRegionAndBorder(Control control, float radius, Graphics graph, Color borderColor)
    {
        using (GraphicsPath roundPath = GetRoundedPath(control.ClientRectangle, radius))
        using (Pen penBorder = new Pen(borderColor, 1))
        {
            graph.SmoothingMode = SmoothingMode.AntiAlias;
            control.Region = new Region(roundPath);
            graph.DrawPath(penBorder, roundPath);
        }
    }
    Private Sub ControlRegionAndBorder(control As Control, radius As Single, graph As Graphics, borderColor As Color)
        Using roundPath As GraphicsPath = GetRoundedPath(control.ClientRectangle, radius)

            Using penBorder As Pen = New Pen(borderColor, 1)
                graph.SmoothingMode = SmoothingMode.AntiAlias
                control.Region = New Region(roundPath)
                graph.DrawPath(penBorder, roundPath)
            End Using
        End Using
    End Sub

Dibujar Rutas – Esquinas del Formulario

private void DrawPath(Rectangle rect, Graphics graph, Color color)
{
    using (GraphicsPath roundPath = GetRoundedPath(rect, borderRadius))
    using (Pen penBorder = new Pen(color, 3))
    {
        graph.DrawPath(penBorder, roundPath);
    }
}
    Private Sub DrawPath(rectForm As Rectangle, graphics As Graphics, color As Color)
        Using roundPath As GraphicsPath = GetRoundedPath(rectForm, borderRadius)
            Using penBorder As Pen = New Pen(color, 3)
                graphics.DrawPath(penBorder, roundPath)
            End Using
        End Using
    End Sub

Obtener Colores de los Limites Exteriores del Formulario

private struct FormBoundsColors
{
    public Color TopLeftColor;
    public Color TopRightColor;
    public Color BottomLeftColor;
    public Color BottomRightColor;
}
private FormBoundsColors GetFormBoundsColors()
{
    var fbColor = new FormBoundsColors();
    using (var bmp = new Bitmap(1, 1))
    using (Graphics graph = Graphics.FromImage(bmp))
    {
        Rectangle rectBmp = new Rectangle(0, 0, 1, 1);

        //Top Left
        rectBmp.X = this.Bounds.X - 1;
        rectBmp.Y = this.Bounds.Y;
        graph.CopyFromScreen(rectBmp.Location, Point.Empty, rectBmp.Size);
        fbColor.TopLeftColor = bmp.GetPixel(0, 0);

        //Top Right
        rectBmp.X = this.Bounds.Right;
        rectBmp.Y = this.Bounds.Y;
        graph.CopyFromScreen(rectBmp.Location, Point.Empty, rectBmp.Size);
        fbColor.TopRightColor = bmp.GetPixel(0, 0);

        //Bottom Left
        rectBmp.X = this.Bounds.X;
        rectBmp.Y = this.Bounds.Bottom;
        graph.CopyFromScreen(rectBmp.Location, Point.Empty, rectBmp.Size);
        fbColor.BottomLeftColor = bmp.GetPixel(0, 0);

        //Bottom Right
        rectBmp.X = this.Bounds.Right;
        rectBmp.Y = this.Bounds.Bottom;
        graph.CopyFromScreen(rectBmp.Location, Point.Empty, rectBmp.Size);
        fbColor.BottomRightColor = bmp.GetPixel(0, 0);
    }
    return fbColor;
}
    Private Structure FormBoundsColors
        Public TopLeftColor As Color
        Public TopRightColor As Color
        Public BottomLeftColor As Color
        Public BottomRightColor As Color
    End Structure
    Private Function GetFormBoundsColors() As FormBoundsColors
        Dim fbColor = New FormBoundsColors()
        Using bmp = New Bitmap(1, 1)
            Using graph As Graphics = Graphics.FromImage(bmp)
                Dim rectBmp As New Rectangle(0, 0, 1, 1)
                'Top Left
                rectBmp.X = Me.Bounds.X - 1
                rectBmp.Y = Me.Bounds.Y
                graph.CopyFromScreen(rectBmp.Location, Point.Empty, rectBmp.Size)
                fbColor.TopLeftColor = bmp.GetPixel(0, 0)
                'Top Right
                rectBmp.X = Me.Bounds.Right
                rectBmp.Y = Me.Bounds.Y
                graph.CopyFromScreen(rectBmp.Location, Point.Empty, rectBmp.Size)
                fbColor.TopRightColor = bmp.GetPixel(0, 0)
                'Bottom Left
                rectBmp.X = Me.Bounds.X
                rectBmp.Y = Me.Bounds.Bottom
                graph.CopyFromScreen(rectBmp.Location, Point.Empty, rectBmp.Size)
                fbColor.BottomLeftColor = bmp.GetPixel(0, 0)
                'Bottom Right
                rectBmp.X = Me.Bounds.Right
                rectBmp.Y = Me.Bounds.Bottom
                graph.CopyFromScreen(rectBmp.Location, Point.Empty, rectBmp.Size)
                fbColor.BottomRightColor = bmp.GetPixel(0, 0)
            End Using
        End Using
        Return fbColor
    End Function

Métodos de Evento

Establecer Región Redondeado y Dibujar Borde + Suavizado (Formulario)

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        //-> SMOOTH OUTER BORDER
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        Rectangle rectForm = this.ClientRectangle;
        int mWidht = rectForm.Width / 2;
        int mHeight = rectForm.Height / 2;
        var fbColors = GetFormBoundsColors();

        //Top Left
        DrawPath(rectForm, e.Graphics, fbColors.TopLeftColor);

        //Top Right
        Rectangle rectTopRight = new Rectangle(mWidht, rectForm.Y, mWidht, mHeight);
        DrawPath(rectTopRight, e.Graphics, fbColors.TopRightColor);

        //Bottom Left
        Rectangle rectBottomLeft = new Rectangle(rectForm.X, rectForm.X + mHeight, mWidht, mHeight);
        DrawPath(rectBottomLeft, e.Graphics, fbColors.BottomLeftColor);

        //Bottom Right
        Rectangle rectBottomRight = new Rectangle(mWidht, rectForm.Y + mHeight, mWidht, mHeight);
        DrawPath(rectBottomRight, e.Graphics, fbColors.BottomRightColor);

        //-> SET ROUNDED REGION AND BORDER
        FormRegionAndBorder(this, borderRadius, e.Graphics, borderColor, borderSize);
    }
    Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
        '-> Smooth Outer Border
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
        Dim rectForm As Rectangle = Me.ClientRectangle
        Dim mWidht As Integer = rectForm.Width / 2
        Dim mHeight As Integer = rectForm.Height / 2
        Dim fbColor = GetFormBoundsColors()

        'Top Left
        DrawPath(rectForm, e.Graphics, fbColor.TopLeftColor)

        'Top Right
        Dim rectTopRight As New Rectangle(rectForm.Right - mWidht, rectForm.Y, mWidht, mHeight)
        DrawPath(rectTopRight, e.Graphics, fbColor.TopRightColor)

        'Bottom Left
        Dim rectBottomLeft As New Rectangle(rectForm.X, rectForm.Bottom - mHeight, mWidht, mHeight)
        DrawPath(rectBottomLeft, e.Graphics, fbColor.BottomLeftColor)

        'Bottom Right
        Dim rectBottomRight As New Rectangle(rectForm.Right - mWidht, rectForm.Bottom - mHeight, mWidht, mHeight)
        DrawPath(rectBottomRight, e.Graphics, fbColor.BottomRightColor)

        '-> Set Rounded Region and Border
        FormRegionAndBorder(Me, borderRadius, e.Graphics, borderColor, borderSize)
    End Sub

Establecer Región Redondeado y Dibujar Borde Fino (Panel Contenedor)

    private void panelContainer_Paint(object sender, PaintEventArgs e)
    {
        ControlRegionAndBorder(panelContainer, borderRadius - (borderSize / 2), e.Graphics, borderColor);
    }
    Private Sub PanelContainer_Paint(sender As Object, e As PaintEventArgs) Handles PanelContainer.Paint
        ControlRegionAndBorder(PanelContainer, borderRadius - (borderSize / 2.0F), e.Graphics, borderColor)
    End Sub

Actualizar Región y Borde

private void Form1_ResizeEnd(object sender, EventArgs e)
{
    this.Invalidate();
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
    this.Invalidate();
}
private void Form1_Activated(object sender, EventArgs e)
{
    this.Invalidate();
}
    Private Sub Form1_ResizeEnd(sender As Object, e As EventArgs) Handles MyBase.ResizeEnd
        Me.Invalidate()
    End Sub

    Private Sub Form1_SizeChanged(sender As Object, e As EventArgs) Handles MyBase.SizeChanged
        Me.Invalidate()
    End Sub

    Private Sub Form1_Activated(sender As Object, e As EventArgs) Handles MyBase.Activated
        Me.Invalidate()
    End Sub

Video Tutorial

C#

Visual Basic