9

I am trying to have a User Control that has rounded corners. It doesnt have a fixed size but it usually doesnt have a width much more than 120 pixels.

I need the User Control and its contents(a label and a table) to have rounded edges and look like a round box.

I have used this code.

[DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
    private static extern IntPtr CreateRoundRectRgn
    (
        int nLeftRect, // x-coordinate of upper-left corner
        int nTopRect, // y-coordinate of upper-left corner
        int nRightRect, // x-coordinate of lower-right corner
        int nBottomRect, // y-coordinate of lower-right corner
        int nWidthEllipse, // height of ellipse
        int nHeightEllipse // width of ellipse
    );

    public static System.Drawing.Region GetRoundedRegion(int controlWidth, int controlHeight)
    {
            return System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, controlWidth - 5, controlHeight - 5, 20, 20));
    } 

This gives the control rounded corners but after it has been running afew times and i have added multiples of my User Control to the form it will cause a leak and i will get the whitebox with a red cross on my user controls.

Is there a better way of doing this?

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
CJM
  • 249
  • 1
  • 4
  • 16
  • 1
    I had this problem too. And believe me, let go of WinForms and start with `WPF`. Way better for customizing!! – kevintjuh93 Oct 07 '15 at 08:50
  • How do you use `CreateRoundRectRgn` ? Similar to [this](http://stackoverflow.com/q/17787573/1997232)? Then of course you will have a handle leakage. Do you want to fix the bug (then post code where you use it) or are looking for alternatives? An alternative could be a [transparent](http://stackoverflow.com/q/9358500/1997232) control with rounded corners images. – Sinatr Oct 07 '15 at 09:02
  • Sinatr, you are correct this is exactly how i am doing this, GetRoundedRegion is being called when the form is repainted, unfortunately i am unable to call this method in the OnHandleCreated() as suggested in your link as the size is highly variable and at handle creation the Controls size is the default size and it goes abit random. – CJM Oct 07 '15 at 11:42

3 Answers3

17

If you want really round corner and not only transparent trick you can use this example:

private int radius = 20;
[DefaultValue(20)]
public int Radius
{
    get { return radius; }
    set
    {
        radius = value;
        this.RecreateRegion();
    }
}

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
private static extern IntPtr CreateRoundRectRgn(int nLeftRect, int nTopRect,
    int nRightRect, int nBottomRect, int nWidthEllipse, int nHeightEllipse);

private GraphicsPath GetRoundRectagle(Rectangle bounds, int radius)
{
    float r = radius;
    GraphicsPath path = new GraphicsPath();
    path.StartFigure();
    path.AddArc(bounds.Left, bounds.Top, r, r, 180, 90);
    path.AddArc(bounds.Right - r, bounds.Top, r, r, 270, 90);
    path.AddArc(bounds.Right - r, bounds.Bottom - r, r, r, 0, 90);
    path.AddArc(bounds.Left, bounds.Bottom - r, r, r, 90, 90);
    path.CloseFigure();
    return path;
}

private void RecreateRegion()
{
    var bounds = ClientRectangle;

    //using (var path = GetRoundRectagle(bounds, this.Radius))
    //    this.Region = new Region(path);

    //Better round rectangle
    this.Region = Region.FromHrgn(CreateRoundRectRgn(bounds.Left, bounds.Top,
        bounds.Right, bounds.Bottom, Radius, radius));
    this.Invalidate();
}

protected override void OnSizeChanged(EventArgs e)
{
    base.OnSizeChanged(e);
    this.RecreateRegion();
}

Region with GraphicsPath:

enter image description here

Region with Windows API:

enter image description here

The difference between this approach and making transparent:

  • Setting round region, the control has really round corners and you can see what is behind the round part despite when it is transparent, you will see background of form.
  • Setting round region, when you click on removed rounded part, click pass through the region and reaches behind, but if you use transparency trick click on transparent region will handle by the control.

You can use any of these 2 options. Making transparent or setting region based on your requirement.

Download

You can download the code or clone the repository here:

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • How would you add a border to this? I need something that is like the FixedSingle BorderStyle, and have that set on the control but when the above code executes it seems to get removed. – Sam W Jan 09 '17 at 16:17
  • @Sam `GetRoundRectagle` returns a path which you can use the path in `e.Graphics.DrawPath()` to draw a border around the path.Just read the last two notes of the answer carefully. (For smoother edges you have the option to keep the region unchanged and just make the control support transparent back color, the difference is just when overlapping some controls. To get a better understanding of what I mean, take a look at [this post](http://stackoverflow.com/questions/35467199/is-this-possible-to-have-triangular-picturebox-instead-of-the-rectangular-one), you may find it useful). – Reza Aghaei Jan 09 '17 at 17:28
2

Setting the Region makes sense only if you want to "click through" the transparent area. If the rounded corners are not so large and you want just to make the corners visually transparent, you can do the same as Button does.

The advantage of this solution that you can have a nice anti-aliased rounded corner here, while the edges of a region are always sharp. Not mentioning that a Region instance is holding unmanaged resources and should be disposed somehow.

protected override void OnPaint(PaintEventArgs e)
{
    PaintTransparentBackground(this, e);
    // TODO: Paint your actual content here with rounded corners
}

private static void PaintTransparentBackground(Control c, PaintEventArgs e)
{
    if (c.Parent == null || !Application.RenderWithVisualStyles)
        return;

    ButtonRenderer.DrawParentBackground(e.Graphics, c.ClientRectangle, c);
}
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
0

I have answered my own question.

Following Sinatr's comment i have found i was unable to use the OnHandleCreated as i needed to be about to paint the object before i knew what its size would be. Following the link Sinatr provided GetRoundedRegion exception

So what i have done is added a IntPtr variable to my UserControl which is asigned on the CreateRoundRectRgn method every paint with the handle. Before this triggers i am using the DeleteObject to remove the old handle.

Not an optimal solution but appears to be working fine for now.

The other suggestions whilst good, wouldnt work in my case.

Community
  • 1
  • 1
CJM
  • 249
  • 1
  • 4
  • 16
  • The answer I posted works properly, you can see the screenshot. And it uses GDI+ :) – Reza Aghaei Oct 07 '15 at 12:16
  • Your method works Reza but the corners are not identical and the sides of my control shrink somewhat. Some fiddling would fix this im sure. I will likely go back and implement your solution in the future when i have some free time as i think it is a better solution, for now im happy with what i have. Cheers though :) – CJM Oct 08 '15 at 09:17