Rasheen Ruwisha
Rasheen Ruwisha

Reputation: 155

Creating Smooth Rounded Corners in WinForm Applications

I want to create a button and a container with rounded corners. I'm using the Region to paint the corners, code attached below. However the corners doesn't seem smooth, is there any way to fix this, any help would be appreciated. Image attached below as im not allowed to upload images yet.

enter image description here

[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
        private static extern IntPtr CreateRoundRectRgn(
            int nLeftRect,
            int nTopRect,
            int nRightRect,
            int nBottomRect,
            int nWidthEllipse,
            int nHeightEllipse
            );
  public Login()
        {
            InitializeComponent();
            this.Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, Width, Height, 30, 30));
            this.logo.Image = Properties.Resources.logo;
            this.btn_login.Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, this.btn_login.Width, this.btn_login.Height, 10, 10));

        }

Upvotes: 3

Views: 14975

Answers (2)

user3985106
user3985106

Reputation:

Try This Code:

using System;
using System.Runtime.InteropServices;

public partial class Form1 : Form
{
    // The enum flag for DwmSetWindowAttribute's second parameter, which tells the function what attribute to set.
    // Copied from dwmapi.h
    public enum DWMWINDOWATTRIBUTE
    {
        DWMWA_WINDOW_CORNER_PREFERENCE = 33
    }

    // The DWM_WINDOW_CORNER_PREFERENCE enum for DwmSetWindowAttribute's third parameter, which tells the function
    // what value of the enum to set.
    // Copied from dwmapi.h
    public enum DWM_WINDOW_CORNER_PREFERENCE
    {
        DWMWCP_DEFAULT      = 0,
        DWMWCP_DONOTROUND   = 1,
        DWMWCP_ROUND        = 2,
        DWMWCP_ROUNDSMALL   = 3
    }

    // Import dwmapi.dll and define DwmSetWindowAttribute in C# corresponding to the native function.
    [DllImport("dwmapi.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
    internal static extern void DwmSetWindowAttribute(IntPtr hwnd,
                                                     DWMWINDOWATTRIBUTE attribute, 
                                                     ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute, 
                                                     uint cbAttribute);
    
    // ...
    // Various other definitions
    // ...
}
  1. call the function in form1 ctor
public Form1()
{
    InitializeComponent();

    var attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
    var preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
    DwmSetWindowAttribute(this.Handle, attribute, ref preference, sizeof(uint));
    
    // ...
    // Perform any other work necessary
    // ...
}

Read More: Apply rounded corners in desktop apps

Upvotes: 0

Hotkey
Hotkey

Reputation: 450

Once the function is not implemented using the normal WinForm function. Therefore we must implement it using win32Api.

The code is created referring to this and this.

First, you have to draw a round rectangle.

public static GraphicsPath RoundedRect(Rectangle bounds, int radius)
{
    int diameter = radius * 2;
    Size size = new Size(diameter, diameter);
    Rectangle arc = new Rectangle(bounds.Location, size);
    GraphicsPath path = new GraphicsPath();

    if (radius == 0)
    {
        path.AddRectangle(bounds);
        return path;
    }

    // top left arc  
    path.AddArc(arc, 180, 90);

    // top right arc  
    arc.X = bounds.Right - diameter;
    path.AddArc(arc, 270, 90);

    // bottom right arc  
    arc.Y = bounds.Bottom - diameter;
    path.AddArc(arc, 0, 90);

    // bottom left arc 
    arc.X = bounds.Left;
    path.AddArc(arc, 90, 90);

    path.CloseFigure();
    return path;
}

public static void FillRoundedRectangle(Graphics graphics, Brush brush, Rectangle bounds, int cornerRadius)
{
    if (graphics == null)
        throw new ArgumentNullException("graphics");
    if (brush == null)
        throw new ArgumentNullException("brush");

    using (GraphicsPath path = RoundedRect(bounds, cornerRadius))
    {
        graphics.FillPath(brush, path);
    }
}

And let's add it to the drawing call.

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics graphics = e.Graphics;

    Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);

    Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f);

    graphics.SmoothingMode = SmoothingMode.HighQuality;
    FillRoundedRectangle(graphics, b, gradientRectangle, 35);
}

Rounded Rectangle Form Then we can draw the same form as the picture above.

Second, draw a form using Per Pixel Alpha Blend.

public void SetBitmap(Bitmap bitmap)
{
    SetBitmap(bitmap, 255);
}

public void SetBitmap(Bitmap bitmap, byte opacity)
{
    if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
        throw new ApplicationException("The bitmap must be 32ppp with alpha-channel.");


    IntPtr screenDc = Win32.GetDC(IntPtr.Zero);
    IntPtr memDc = Win32.CreateCompatibleDC(screenDc);
    IntPtr hBitmap = IntPtr.Zero;
    IntPtr oldBitmap = IntPtr.Zero;

    try
    {
        hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
        oldBitmap = Win32.SelectObject(memDc, hBitmap);

        Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);
        Win32.Point pointSource = new Win32.Point(0, 0);
        Win32.Point topPos = new Win32.Point(Left, Top);
        Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();
        blend.BlendOp = Win32.AC_SRC_OVER;
        blend.BlendFlags = 0;
        blend.SourceConstantAlpha = opacity;
        blend.AlphaFormat = Win32.AC_SRC_ALPHA;

        Win32.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA);
    }
    finally
    {
        Win32.ReleaseDC(IntPtr.Zero, screenDc);
        if (hBitmap != IntPtr.Zero)
        {
            Win32.SelectObject(memDc, oldBitmap);
            Win32.DeleteObject(hBitmap);
        }

        Win32.DeleteDC(memDc);
    }
}

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x00080000;
        return cp;
    }
}

Finally, call SetBitmap when loading a form.

private void Form1_Load(object sender, EventArgs e)
{
    Bitmap myBitmap = new Bitmap(this.Width, this.Height);

    Graphics graphics = Graphics.FromImage(myBitmap);

    Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);

    Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f);

    graphics.SmoothingMode = SmoothingMode.HighQuality;
    FillRoundedRectangle(graphics, b, gradientRectangle, 35);

    SetBitmap(myBitmap);
}

When you finish the above tasks, you can finally get Smooth Round Corners in WinForm Applications.

Smooth Round Corners Form


I added it to the full code, but I forgot to explain it

how-draw-a-control-over-a-ws-ex-layered-form

Note that when using UpdateLayeredWindow the application doesn't need to respond to WM_PAINT or other painting messages, because it has already provided the visual representation for the window and the system will take care of storing that image, composing it, and rendering it on the screen. UpdateLayeredWindow is quite powerful, but it often requires modifying the way an existing Win32 application draws.

You must manually call the drawing using a timer, etc.

private Timer drawTimer = new Timer();

protected override void OnLoad(EventArgs e)
{
    if (!DesignMode)
    {
        drawTimer.Interval = 1000 / 60;
        drawTimer.Tick += DrawForm;
        drawTimer.Start();
    }
    base.OnLoad(e);
}

private void DrawForm(object pSender, EventArgs pE)
{
    using (Bitmap backImage = new Bitmap(this.Width, this.Height))
    {
        using (Graphics graphics = Graphics.FromImage(backImage))
        {
            Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
            using (Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f))
            {
                graphics.SmoothingMode = SmoothingMode.HighQuality;

                RoundedRectangle.FillRoundedRectangle(graphics, b, gradientRectangle, 35);

                foreach (Control ctrl in this.Controls)
                {
                    using (Bitmap bmp = new Bitmap(ctrl.Width, ctrl.Height))
                    {
                        Rectangle rect = new Rectangle(0, 0, ctrl.Width, ctrl.Height);
                        ctrl.DrawToBitmap(bmp, rect);
                        graphics.DrawImage(bmp, ctrl.Location);
                    }
                }

                SetBitmap(backImage, Left, Top, Handle);
            }
        }
    }
}


protected override void OnPaint(PaintEventArgs e)
{
    if (DesignMode) // Add DesignMode check
    {
        ...
    }

    base.OnPaint(e);
}

Full code of Form

public class RoundedForm : Form
{
    private Timer drawTimer = new Timer();

    public RoundedForm()
    {
        this.FormBorderStyle = FormBorderStyle.None;
    }

    protected override void OnLoad(EventArgs e)
    {
        if (!DesignMode)
        {
            drawTimer.Interval = 1000 / 60;
            drawTimer.Tick += DrawForm;
            drawTimer.Start();
        }
        base.OnLoad(e);
    }

    private void DrawForm(object pSender, EventArgs pE)
    {
        using (Bitmap backImage = new Bitmap(this.Width, this.Height))
        {
            using (Graphics graphics = Graphics.FromImage(backImage))
            {
                Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
                using (Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f))
                {
                    graphics.SmoothingMode = SmoothingMode.HighQuality;

                    RoundedRectangle.FillRoundedRectangle(graphics, b, gradientRectangle, 35);

                    foreach (Control ctrl in this.Controls)
                    {
                        using (Bitmap bmp = new Bitmap(ctrl.Width, ctrl.Height))
                        {
                            Rectangle rect = new Rectangle(0, 0, ctrl.Width, ctrl.Height);
                            ctrl.DrawToBitmap(bmp, rect);
                            graphics.DrawImage(bmp, ctrl.Location);
                        }
                    }

                    PerPixelAlphaBlend.SetBitmap(backImage, Left, Top, Handle);
                }
            }
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        if (DesignMode)
        {
            Graphics graphics = e.Graphics;

            Rectangle gradientRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);

            Brush b = new LinearGradientBrush(gradientRectangle, Color.DarkSlateBlue, Color.MediumPurple, 0.0f);

            graphics.SmoothingMode = SmoothingMode.HighQuality;

            RoundedRectangle.FillRoundedRectangle(graphics, b, gradientRectangle, 35);
        }

        base.OnPaint(e);
    }


    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            if (!DesignMode)
            {
                cp.ExStyle |= 0x00080000;
            }
            return cp;
        }
    }
}

public static class RoundedRectangle
{
    public static GraphicsPath RoundedRect(Rectangle bounds, int radius)
    {
        int diameter = radius * 2;
        Size size = new Size(diameter, diameter);
        Rectangle arc = new Rectangle(bounds.Location, size);
        GraphicsPath path = new GraphicsPath();

        if (radius == 0)
        {
            path.AddRectangle(bounds);
            return path;
        }

        // top left arc  
        path.AddArc(arc, 180, 90);

        // top right arc  
        arc.X = bounds.Right - diameter;
        path.AddArc(arc, 270, 90);

        // bottom right arc  
        arc.Y = bounds.Bottom - diameter;
        path.AddArc(arc, 0, 90);

        // bottom left arc 
        arc.X = bounds.Left;
        path.AddArc(arc, 90, 90);

        path.CloseFigure();
        return path;
    }

    public static void FillRoundedRectangle(Graphics graphics, Brush brush, Rectangle bounds, int cornerRadius)
    {
        if (graphics == null)
            throw new ArgumentNullException("graphics");
        if (brush == null)
            throw new ArgumentNullException("brush");

        using (GraphicsPath path = RoundedRect(bounds, cornerRadius))
        {
            graphics.FillPath(brush, path);
        }
    }
}

internal static class PerPixelAlphaBlend
{
    public static void SetBitmap(Bitmap bitmap, int left, int top, IntPtr handle)
    {
        SetBitmap(bitmap, 255, left, top, handle);
    }

    public static void SetBitmap(Bitmap bitmap, byte opacity, int left, int top, IntPtr handle)
    {
        if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
            throw new ApplicationException("The bitmap must be 32ppp with alpha-channel.");


        IntPtr screenDc = Win32.GetDC(IntPtr.Zero);
        IntPtr memDc = Win32.CreateCompatibleDC(screenDc);
        IntPtr hBitmap = IntPtr.Zero;
        IntPtr oldBitmap = IntPtr.Zero;

        try
        {
            hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
            oldBitmap = Win32.SelectObject(memDc, hBitmap);

            Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);
            Win32.Point pointSource = new Win32.Point(0, 0);
            Win32.Point topPos = new Win32.Point(left, top);
            Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();
            blend.BlendOp = Win32.AC_SRC_OVER;
            blend.BlendFlags = 0;
            blend.SourceConstantAlpha = opacity;
            blend.AlphaFormat = Win32.AC_SRC_ALPHA;

            Win32.UpdateLayeredWindow(handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA);
        }
        finally
        {
            Win32.ReleaseDC(IntPtr.Zero, screenDc);
            if (hBitmap != IntPtr.Zero)
            {
                Win32.SelectObject(memDc, oldBitmap);
                Win32.DeleteObject(hBitmap);
            }

            Win32.DeleteDC(memDc);
        }
    }
}

internal class Win32
{
    public enum Bool
    {
        False = 0,
        True
    };


    [StructLayout(LayoutKind.Sequential)]
    public struct Point
    {
        public Int32 x;
        public Int32 y;

        public Point(Int32 x, Int32 y) { this.x = x; this.y = y; }
    }


    [StructLayout(LayoutKind.Sequential)]
    public struct Size
    {
        public Int32 cx;
        public Int32 cy;

        public Size(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; }
    }


    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct ARGB
    {
        public byte Blue;
        public byte Green;
        public byte Red;
        public byte Alpha;
    }


    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct BLENDFUNCTION
    {
        public byte BlendOp;
        public byte BlendFlags;
        public byte SourceConstantAlpha;
        public byte AlphaFormat;
    }


    public const Int32 ULW_COLORKEY = 0x00000001;
    public const Int32 ULW_ALPHA = 0x00000002;
    public const Int32 ULW_OPAQUE = 0x00000004;

    public const byte AC_SRC_OVER = 0x00;
    public const byte AC_SRC_ALPHA = 0x01;


    [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern Bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);

    [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern IntPtr GetDC(IntPtr hWnd);

    [DllImport("user32.dll", ExactSpelling = true)]
    public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern IntPtr CreateCompatibleDC(IntPtr hDC);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern Bool DeleteDC(IntPtr hdc);

    [DllImport("gdi32.dll", ExactSpelling = true)]
    public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern Bool DeleteObject(IntPtr hObject);
}

Upvotes: 14

Related Questions