I'm running into the problem that my rounded corners method returns non-uniform corners.
Method:
GraphicsPath GetRoundedPath(Rectangle r, int radius, bool topRight = true, bool topLeft = true, bool bottomLeft = true, bool bottomRight = true)
{
GraphicsPath path = new GraphicsPath();
if (topLeft)
path.AddArc(r.X, r.Y, radius, radius, 180, 90);
else path.AddLine(r.X, r.Y, r.Right, r.Y);
if (topRight)
path.AddArc(r.Right - radius, r.Y, radius, radius, 270, 90);
else path.AddLine(r.Right, r.Y, r.Right, r.Height);
if (bottomRight)
path.AddArc(r.Right - radius, r.Bottom - radius, radius, radius, 0, 90);
else path.AddLine(r.Right, r.Bottom, r.Right, r.Height);
if (bottomLeft)
path.AddArc(r.X, r.Bottom - radius, radius, radius, 90, 90);
else path.AddLine(r.X, r.Bottom, r.X, r.Y);
return path;
}
When invoke:
Panel p = new Panel();
p.BackColor = Color.Red;
p.Size = new Size(200, 200);
Controls.Add(p);
p.Region = new Region(GetRoundedPath(new Rectangle(p.ClientRectangle, 30, true, true, true, true));
p.Location = new Point(200, 200);
Return:
It is noticeable that the upper left corner is drawn normally, the upper right and lower left are crooked, and the lower left is even crooked.
After, I noticed that exactly one pixel was not visible on the right and bottom, then I opened the Paint(program) and painted it myself and got even corners:
What I tried:
Passed less Rectangle to GetRoundedPath
GetRoundedPath(new Rectangle(p.ClientRectangle.X, p.ClientRectangle.Y, p.ClientRectangle.Width - 1, p.ClientRectangle.Height - 1), 30, true, true, true, true)
The control just became a pixel smaller in width and height.
Don't to change Control.Region, to use Graphics.FillPath instead
p.Paint += (s, e) =>
{
e.Graphics.FillPath(Brushes.Red, GetRoundedPath(p.ClientRectangle, 30, true, true, true, true));
};
Nothing has changed, I also tried to use it in conjunction with the first
Increase radius for individual corners
int oppositeRadius = (int)Math.Round(radius * 1.5)
...
The corners became stretched and looked more like slanted lines
UPD: I found similar post - Strange GraphicsPath.AddArc() behaviour but it work only with Region and all corners, when I have problem with GraphicsPath
It looks like off by 1 pixel for center of arc error. Maybe try something like:
GraphicsPath GetRoundedPath(Rectangle r, int radius, bool topRight = true, bool topLeft = true, bool bottomLeft = true, bool bottomRight = true)
{
GraphicsPath path = new GraphicsPath();
if (topLeft)
path.AddArc(r.X, r.Y, radius, radius, 180, 90);
else path.AddLine(r.X, r.Y, r.Right, r.Y);
if (topRight)
path.AddArc(r.Right - radius - 1, r.Y, radius, radius, 270, 90);
else path.AddLine(r.Right, r.Y, r.Right, r.Height);
if (bottomRight)
path.AddArc(r.Right - radius - 1, r.Bottom - radius - 1, radius, radius, 0, 90);
else path.AddLine(r.Right, r.Bottom, r.Right, r.Height);
if (bottomLeft)
path.AddArc(r.X, r.Bottom - radius - 1, radius, radius, 90, 90);
else path.AddLine(r.X, r.Bottom, r.X, r.Y);
return path;
}
Related
I have been looking for quite a while for an already pre-made function for creating a rounded rectangle that doesn't cause tearing/glitching like the one below. Credits to #György Kőszeg. This function works fine if the rectangle is big enough. When you start making the rectangle smaller you run into issues like the below image. I am looking for an easy fix for this.
If this issue is on this website and I have missed it, I do apologize for re-asking. (I remember asking this a while back either on here or on another website and receiving an answer that worked amazingly) This issue has been paining me for quite awhile (again).
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;
}
UPDATE:
In the meantime I have started to use the code below which just overrides the rectangle if the width is less than the height. This creates a perfect circle (the smallest the rounded rectangle can be without the glitching/tearing). A comment was placed on this stating I should not use "tearing" because its simply caused by the math which I understand but I really have no idea what else to call the glitchy rectangle in the image.
Basically I want a oval instead of a circle to correctly reflex the "Exp" value.
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();
//new code here//
if(bounds.Height >= bounds.Width)
{
bounds.Width = bounds.Height;
}
if (radius >= diameter) {
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;
}
Something like this may work better. Try it with various values for curveSize. Note that curveSize must be between 1 and the minimum of rect.Width/4 and rect.Height/4:
public static GraphicsPath RoundedRect(Rectangle rc, float curveSize)
{
if (curveSize < 0 || curveSize > rc.Height / 4.0f || curveSize > rc.Width / 4.0f)
curveSize = 0;
var result = new GraphicsPath();
if (curveSize > 0)
{
float size4 = curveSize * 4;
result.AddArc(rc.Right - size4, rc.Top, size4, size4, 270, 90);
result.AddArc(rc.Right - size4, rc.Bottom - size4, size4, size4, 0, 90);
result.AddArc(rc.Left, rc.Bottom - size4, size4, size4, 90, 90);
result.AddArc(rc.Left, rc.Top, size4, size4, 180, 90);
result.CloseFigure();
}
else if (rc.Width > 0 && rc.Height > 0)
{
result.AddRectangle(rc);
}
return result;
}
Both labels have AutoSize true & TextAlign MiddleCenter.
How can also label2 show smooth borders?
Here is the test code for handlers Form.Load(...) & Form.Paint(...):
int _cornerRadius = 10;
Point _locationLabel2;
// Form.Load(...)
private void Form3_Load(object sender, EventArgs e)
{
// Step 1: Cut the label regions (seems to be ok, result is the same for both labels)
GraphicsPath graphicsPath = _getRoundPath(label1.ClientRectangle, _cornerRadius);
label1.Region = new Region(graphicsPath);
graphicsPath = _getRoundPath(label2.ClientRectangle, _cornerRadius);
label2.Region = new Region(graphicsPath);
_locationLabel2 = this.PointToClient(label2.Parent.PointToScreen(label2.Location));
}
// Form.Paint(...)
private void Form3_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(label1.BackColor, 3.0f))
{
// Step 2: Smooth the label borders (ok only for label1)
_drawRoundedRectangle(e.Graphics, pen, label1.Location.X, label1.Location.Y,
label1.ClientRectangle.Width, label1.ClientRectangle.Height, _cornerRadius);
_drawRoundedRectangle(e.Graphics, pen, _locationLabel2.X, _locationLabel2.Y,
label2.ClientRectangle.Width, label2.ClientRectangle.Height, _cornerRadius);
}
}
// Helper 1/3
private static GraphicsPath _getRoundPath(Rectangle rectangle, int radius)
{
int x = rectangle.X;
int y = rectangle.Y;
int width = rectangle.Width;
int height = rectangle.Height;
radius = radius << 1;
GraphicsPath path = new GraphicsPath();
if (radius > 0)
{
if (radius > height) radius = height;
if (radius > width) radius = width;
path.AddArc(x, y, radius, radius, 180, 90);
path.AddArc(x + width - radius, y, radius, radius, 270, 90);
path.AddArc(x + width - radius, y + height - radius, radius, radius, 0, 90);
path.AddArc(x, y + height - radius, radius, radius, 90, 90);
path.CloseFigure();
}
else
{
path.AddRectangle(rectangle);
}
return path;
}
// Helper 2/3
private void _drawRoundedRectangle(Graphics graphics, Pen pen, int x, int y, int width, int height, int radius)
{
RectangleF rectangle = new RectangleF(x, y, width, height);
GraphicsPath path = _generateRoundedRectangle(graphics, rectangle, radius);
SmoothingMode old = graphics.SmoothingMode;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.DrawPath(pen, path);
graphics.SmoothingMode = old;
}
// Helper 3/3
private static GraphicsPath _generateRoundedRectangle(Graphics graphics, RectangleF rectangle, int radius)
{
GraphicsPath path = new GraphicsPath();
float diameter = radius * 2.0F;
SizeF sizeF = new SizeF(diameter, diameter);
RectangleF arc = new RectangleF(rectangle.Location, sizeF);
path.AddArc(arc, 180, 90);
arc.X = rectangle.Right - diameter;
path.AddArc(arc, 270, 90);
arc.Y = rectangle.Bottom - diameter;
path.AddArc(arc, 0, 90);
arc.X = rectangle.Left;
path.AddArc(arc, 90, 90);
path.CloseFigure();
return path;
}
Main code parts are from Arun Reginald Zaheeruddin
Solved it according to this answer by #Reza Aghaei.
Solution
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class RoundLabel : Label
{
[Browsable(true)]
public Color _BackColor { get; set; }
public RoundLabel()
{
this.DoubleBuffered = true;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var graphicsPath = _getRoundRectangle(this.ClientRectangle))
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var brush = new SolidBrush(_BackColor))
e.Graphics.FillPath(brush, graphicsPath);
using (var pen = new Pen(_BackColor, 1.0f))
e.Graphics.DrawPath(pen, graphicsPath);
TextRenderer.DrawText(e.Graphics, Text, this.Font, this.ClientRectangle, this.ForeColor);
}
}
private GraphicsPath _getRoundRectangle(Rectangle rectangle)
{
int cornerRadius = 15; // change this value according to your needs
int diminisher = 1;
GraphicsPath path = new GraphicsPath();
path.AddArc(rectangle.X, rectangle.Y, cornerRadius, cornerRadius, 180, 90);
path.AddArc(rectangle.X + rectangle.Width - cornerRadius - diminisher, rectangle.Y, cornerRadius, cornerRadius, 270, 90);
path.AddArc(rectangle.X + rectangle.Width - cornerRadius - diminisher, rectangle.Y + rectangle.Height - cornerRadius - diminisher, cornerRadius, cornerRadius, 0, 90);
path.AddArc(rectangle.X, rectangle.Y + rectangle.Height - cornerRadius - diminisher, cornerRadius, cornerRadius, 90, 90);
path.CloseAllFigures();
return path;
}
}
I made a function that draws a rectangle with rounded corners using examples I got researching here, now I want to call this function with a button click, but I'm not sure how to provide the arguments to the function, can someone help in how to I call this function?
public void DrawCond(Graphics g, Pen p, float width, float height, float x, float y)
{
// Auxiliary variables
float radius;
if (width>=3.55)
{
radius = 1F;
}
else if (width>2.25)
{
radius = 0.8F;
}
else if (width>1.61)
{
radius = 0.65F;
}
else
{
radius = 0.5F;
}
GraphicsPath gp = new GraphicsPath();
//Draw lines
gp.AddLine(x + radius, y, x + width - 2 * radius, y); //bottom horizontal line
gp.AddLine(x + radius, y + height, x + width - 2 * radius, y + height); //top horizontal line
gp.AddLine(x + width, y + radius, x + width, y + height - 2 * radius); //inner vertical line
gp.AddLine(x + radius, y + radius, x + radius, y + height - 2 * radius); //outer vertical line
//Draw arcs
gp.AddArc(x, y + radius, radius, radius, 90, 90); //bottom left corner
gp.AddArc(x + width - radius, y + radius, radius, radius, 0, 90); //bottom right corner
gp.AddArc(x, y + height, radius, radius, 180, 90); //top left corner
gp.AddArc(x + width - radius, y + height, radius, radius, 270, 90); //top right corner
g.DrawPath(p, gp);
gp.Dispose();
}
You can create Graphics from panel you want to draw to like var g = panel.CreateGraphics() and pass it to your function.
Also you can create Pen by using one of its constructors. See MSDN for reference about Pen.
I'm trying to create custom control that will be used to present timeline.
I found nice looking design on net:
and I started coding, couple of minutes later I had this:
As You can see big marker looks quite good, but smallest could look better.
My current code responsible for marker drawing looks like this:
enum Position
{
Left,
Right
}
private void DrawMarker(Graphics gfx, Rectangle Bounds, int CornerRadius, Pen DrawPen, Position direction)
{
GraphicsPath gfxPath = new GraphicsPath();
gfxPath.AddArc(new Rectangle(Bounds.X + Bounds.Width - CornerRadius, Bounds.Y + 0, CornerRadius, CornerRadius), 270, 45); //tr
if(direction==Position.Right)
gfxPath.AddArc(new Rectangle(Bounds.X + Bounds.Width - CornerRadius + 40, Bounds.Y + Bounds.Height / 2, CornerRadius, CornerRadius), 315, 90); //right
gfxPath.AddArc(new Rectangle(Bounds.X + Bounds.Width - CornerRadius, Bounds.Y + Bounds.Height - CornerRadius, CornerRadius, CornerRadius), 45, 45); //br
gfxPath.AddArc(new Rectangle(Bounds.X + 0, Bounds.Y + Bounds.Height - CornerRadius, CornerRadius, CornerRadius), 90, 90); //bl
gfxPath.AddArc(new Rectangle(Bounds.X + 0, Bounds.Y + 0, CornerRadius, CornerRadius), 180, 90); //tl
gfxPath.CloseAllFigures();
//gfx.FillPath(new SolidBrush(FillColor), gfxPath);
gfx.DrawPath(DrawPen, gfxPath);
}
I need a way to improve look of those markers.
I think that problem are angles of arcs in top right and bottom right corners.
I have them set to 45, but this looks bad if markes is small.
Angles on left side will be always 90, but angles on right and on marked need to be calculated.
How should I modify my function to calculate arcs correctly?
It looks like the "nose" of the tag rectangle isn't compensating for the radius size, which is causing the "nose" to dip downward:
r.Y + r.Height / 2 - radius / 2
I also shortened the width from 40 to 20.
private void DrawMarker(Graphics gfx, Rectangle r, int radius, Pen drawPen, Position direction) {
using (GraphicsPath gfxPath = new GraphicsPath()) {
gfxPath.AddArc(new Rectangle(r.X + r.Width - radius, r.Y + 0, radius, radius), 270, 45); //tr
if (direction == Position.Right) {
gfxPath.AddArc(new Rectangle(r.X + r.Width - radius + 20, r.Y + r.Height / 2 - radius / 2, radius, radius), 315, 90); //right
}
gfxPath.AddArc(new Rectangle(r.X + r.Width - radius, r.Y + r.Height - radius, radius, radius), 45, 45); //br
gfxPath.AddArc(new Rectangle(r.X + 0, r.Y + r.Height - radius, radius, radius), 90, 90); //bl
gfxPath.AddArc(new Rectangle(r.X + 0, r.Y + 0, radius, radius), 180, 90); //tl
gfxPath.CloseAllFigures();
gfx.DrawPath(drawPen, gfxPath);
}
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen pen = new Pen(Color.DarkGray, 2)) {
DrawMarker(e.Graphics, new Rectangle(16, 16, 100, 32), 8, pen, Position.Right);
}
base.OnPaint(e);
}
Result:
I have a UserControl which has a button on it. On the UserControl OnPaint event I draw a rounded corner border (or a simple rectangle if the radius is zero) and then I fill the entire control. After these manipulations my Button (btnClose) disappears.
How do I make my button visible again?
protected override void OnPaint(PaintEventArgs pe)
{
using (System.Drawing.Pen p = new Pen(new SolidBrush(this.BorderColor)))
{
if (borderRadius > 0)
{
DrawRoundRect(pe.Graphics, p, 0, 0, this.Width - 1, this.Height - 1, borderRadius, this.FillColor);
}
else
{
this.BackColor = this.FillColor;
pe.Graphics.DrawRectangle(p, 0, 0, this.Width - 1, this.Height - 1);
}
btnClose.Location = new Point(this.Width - btnClose.Width - BTN_MARGIN_DELTA, BTN_MARGIN_DELTA);
}
base.OnPaint(pe);
}
Just in case, the DrawRoundRect function:
void DrawRoundRect(Graphics g, Pen p, float X, float Y, float width, float height, float radius, Color _fillColor)
{
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddLine(X + radius, Y, X + width - (radius * 2), Y);
gp.AddArc(X + width - (radius * 2), Y, radius * 2, radius * 2, 270, 90);
gp.AddLine(X + width, Y + radius, X + width, Y + height - (radius * 2));
gp.AddArc(X + width - (radius * 2), Y + height - (radius * 2), radius * 2, radius * 2, 0, 90);
gp.AddLine(X + width - (radius * 2), Y + height, X + radius, Y + height);
gp.AddArc(X, Y + height - (radius * 2), radius * 2, radius * 2, 90, 90);
gp.AddLine(X, Y + height - (radius * 2), X, Y + radius);
gp.AddArc(X, Y, radius * 2, radius * 2, 180, 90);
gp.CloseFigure();
using (SolidBrush brush = new SolidBrush(_fillColor))
{
g.FillPath(brush, gp);
g.DrawPath(p, gp);
}
}
}
Try moving the location code to the resize method:
protected override void OnResize(EventArgs e) {
btnClose.Location = new Point(this.Width - btnClose.Width - BTN_MARGIN_DELTA, BTN_MARGIN_DELTA);
}
Moving controls in a paint event could cause recursive calls to the paint event. Only "paint" in a paint event.
I set FillColor = Color.Gray, BorderColor = Color.Black, borderRadius = 5, BTN_MARGIN_DELTA = 2 and it seems to work without any problem. Here is a screenshot:
I think the problem isn't these lines of code.
Well, my mistake. It was a function that deletes all controls from UserControl. So I filter the controls on removal.
void ClearControls()
{
for (int i = 0; i < Items.Count; i++)
{
foreach (Control cc in Controls)
{
if (cc.Name.Contains(LINK_LABEL_FAMILY) || (cc.Name.Contains(LABEL_FAMILY)))
{
Controls.RemoveByKey(cc.Name);
}
}
}
}