Every sample code I've ever found for drawing rounded rectangles using GDI+ goes something like this (lifted and slightly modified from BobPowell.net):
Private Sub Panel1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Panel1.Paint
e.Graphics.Clear(SystemColors.Window)
e.Graphics.SmoothingMode = SmoothingMode.None
Call DrawRoundRect(e.Graphics, Pens.Red, 10, 10, 48, 24, 6)
End Sub
Public Sub DrawRoundRect(ByVal g As Graphics, ByVal p As Pen, ByVal x As Single, ByVal y As Single, ByVal width As Single, ByVal height As Single, ByVal radius As Single)
Using gp As New GraphicsPath()
gp.StartFigure()
gp.AddArc(x + width - radius, y, radius * 2, radius * 2, 270, 90)
gp.AddArc(x + width - radius, y + height - radius, radius * 2, radius * 2, 0, 90)
gp.AddArc(x, y + height - radius, radius * 2, radius * 2, 90, 90)
gp.AddArc(x, y, radius * 2, radius * 2, 180, 90)
gp.CloseFigure()
g.DrawPath(p, gp)
End Using
End Sub
This produces a rounded rectangle where only the top left corner is accurate.
AntiAliasing has to be turned off because it is going through a remote desktop connection, and I can't depend on it being available. Besides, I am looking for a crisp rounded rectangle.
I've tried resizing the other corners and changing the pen alignments, but nothing seems to produce a simple, accurate rounded rectangle.
Is there a way to draw a better rounded rectangle than this in good old winforms?
I have found the best solution to be just old-school Windows API:
Private Sub DrawRoundRect(ByVal g As Graphics, ByVal r As Rectangle)
Dim hDC As IntPtr = g.GetHdc
Dim hPen As IntPtr = CreatePen(PS_SOLID, 0, ColorTranslator.ToWin32(Color.Red))
Dim hOldPen As IntPtr = SelectObject(hDC, hPen)
SelectObject(hDC, GetStockObject(NULL_BRUSH))
RoundRect(hDC, r.Left, r.Top, r.Right - 1, r.Bottom - 1, 12, 12)
SelectObject(hDC, hOldPen)
DeleteObject(hPen)
g.ReleaseHdc(hDC)
End Sub
This produces the symmetrical rounded rectangle I've been looking for:
On occasion, I've used a "low tech" approach to deal with the rounding errors in GDI+
1) Resize your source image to a binary multiple of its original size. Typically, I'll resample to a width and height 4 times greater (or 8, or 16) than the original.
2) Perform all my GDI+ drawing operations (taking into account, of course, that my co-ordinates will need to be multiplied by a factor of 4). There is no need to use any fancy anti-aliasing.
3) Re-sample the image back down to the original dimensions. Shrinking the image results in a nice smoothing effect, and minimizes any rounding errors in lines, curves, etc.
Because no-one's answered you yet here is a trick I have used in the past. It works reasonably well, and definitely looks better than the classic implementation with AddArc().
It uses circles and clipping to achieve the result you want. It may show slight artefacts when using pens with a width greater than 1px, but other than that it works well.
I hope it will be good enough for your project.
private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
{
g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
g.DrawLine(pen, rect.Right, rect.Top+radius, rect.Right, rect.Bottom - radius);
g.DrawLine(pen, rect.Left + radius, rect.Bottom, rect.Right - radius, rect.Bottom);
g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);
g.SetClip(new Rectangle(rect.Left, rect.Top, radius, radius));
g.DrawEllipse(pen, rect.Left, rect.Top, radius * 2, radius * 2);
g.ResetClip();
g.SetClip(new Rectangle(rect.Right-radius, rect.Top, radius+1, radius+1));
g.DrawEllipse(pen, rect.Right - radius * 2, rect.Top, radius * 2, radius * 2);
g.ResetClip();
g.SetClip(new Rectangle(rect.Right - radius, rect.Bottom-radius, radius+1, radius+1));
g.DrawEllipse(pen, rect.Right - radius * 2, rect.Bottom - (radius * 2), radius * 2, radius * 2);
g.ResetClip();
g.SetClip(new Rectangle(rect.Left, rect.Bottom - radius, radius+1, radius+1));
g.DrawEllipse(pen, rect.Left, rect.Bottom - (radius * 2), radius * 2, radius * 2);
g.ResetClip();
}
The method's interface is straightforward, but post a comment if you need assistance.
Edit: Something else that should work is to draw the same arc four times, but flipped using TranslateTransform and TranslateScale. That should mean the arc appears identical in each corner.
private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
{
g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
g.DrawLine(pen, rect.Right-1, rect.Top+radius, rect.Right-1, rect.Bottom - radius);
g.DrawLine(pen, rect.Left + radius, rect.Bottom-1, rect.Right - radius, rect.Bottom-1);
g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);
g.TranslateTransform(rect.Left, rect.Top);
g.DrawArc(pen, 0, 0, radius * 2, radius * 2, 180, 90);
g.ResetTransform();
g.TranslateTransform(rect.Right, rect.Top);
g.ScaleTransform(-1, 1);
g.DrawArc(pen, 1, 0, radius * 2, radius * 2, 180, 90);
g.ResetTransform();
g.TranslateTransform(rect.Right, rect.Bottom);
g.ScaleTransform(-1, -1);
g.DrawArc(pen, 1, 1, radius * 2, radius * 2, 180, 90);
g.ResetTransform();
g.TranslateTransform(rect.Left, rect.Bottom);
g.ScaleTransform(1, -1);
g.DrawArc(pen, 0, 1, radius * 2, radius * 2, 180, 90);
g.ResetTransform();
}
This is similar to the old Computer Graphics method of drawing a circle, where you'd draw a quarter circle four times to avoid rounding errors such as the one in GDI.
Another alternative is to draw the first arc onto an image, and then draw the image four times, flipping as required. Below is a variation on the second method, using an image to draw the arcs.
private void DrawRoundedRectangle(Graphics g, Pen pen, Rectangle rect, int radius)
{
g.DrawLine(pen, rect.Left + radius, rect.Top, rect.Right - radius, rect.Top);
g.DrawLine(pen, rect.Right - 1, rect.Top + radius, rect.Right - 1, rect.Bottom - radius);
g.DrawLine(pen, rect.Left + radius, rect.Bottom - 1, rect.Right - radius, rect.Bottom - 1);
g.DrawLine(pen, rect.Left, rect.Top + radius, rect.Left, rect.Bottom - radius);
Bitmap arc = new Bitmap(radius, radius, g);
Graphics.FromImage(arc).DrawArc(pen, 0, 0, radius * 2, radius * 2, 180, 90);
g.TranslateTransform(rect.Left, rect.Top);
g.DrawImage(arc, 0, 0);
g.ResetTransform();
g.TranslateTransform(rect.Right, rect.Top);
g.ScaleTransform(-1, 1);
g.DrawImage(arc, 0, 0);
g.ResetTransform();
g.TranslateTransform(rect.Right, rect.Bottom);
g.ScaleTransform(-1, -1);
g.DrawImage(arc, 0, 0);
g.ResetTransform();
g.TranslateTransform(rect.Left, rect.Bottom);
g.ScaleTransform(1, -1);
g.DrawImage(arc, 0, 0);
g.ResetTransform();
arc.Dispose();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With