I am currently trying to draw a gradient filled rounded rectangle onto a panel bar in a form.
From some research I found some code that allowed me to create a custom rectangle :
static class CustomRectangle
{
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(this 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);
}
}
Credit to this page
Next using this custom rectangle, I have tried using the paint method for the bar panel.
private void quickMenuBar_Paint(object sender, PaintEventArgs e)
{
LinearGradientBrush myBrush = new LinearGradientBrush(new Point(20, 20), new Point(120, 520), Color.DarkBlue, Color.RoyalBlue);
System.Drawing.Graphics formGraphics = this.CreateGraphics();
CustomRectangle.FillRoundedRectangle(formGraphics, myBrush, new System.Drawing.Rectangle(20, 20, 100, 500), 25);
myBrush.Dispose();
formGraphics.Dispose();
}
But after executing this code, it only prints a rounded rectangle directly onto the form and behind the bar panel.
I have other methods that fill a panel with a standard rectangle using PaintEventArgs e:
e.Graphics.FillRectangle(myBrush , otherBar.ClientRectangle);
So obviously I need to use PaintEventArgs e in my custom rectangle method, but I do not know how or where to.
If there are better ways than this way of drawing a rounded rectnagles, please share.
You generally should never be using CreateGraphics(). Simply remove this line:
System.Drawing.Graphics formGraphics = this.CreateGraphics();
And use e.Graphics where you would previously use formGraphics. The Paint event is basically asking you to "paint something for me, here's the graphics object to paint on".
Since you're already providing a Graphics object instance to your rounded rectangle method, no changes are required there.
Related
I have a Form which contains:
a TrackBar (minimum = 1, maximum = 200, represents zoom percent);
a UserControl with BorderStyle = BorderStyle.None.
Relevant code
Form1
From designer code
trackBar1.Value = 100;
BackColor = Color.Gray;
From code-behind
private void trackBar1_Scroll(object sender, EventArgs e)
{
userControl11.SetZoomFactor(trackBar1.Value / 100F);
}
UserControl1
internal float MyBaseWidth;
public UserControl1()
{
InitializeComponent();
MyBaseWidth = Width;
SetZoomFactor(1);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
Pen p = new Pen(Color.Yellow);
e.Graphics.DrawPath(p, GraphicsPathWithBorder);
}
internal GraphicsPath GraphicsPathWithBorder;
internal void SetZoomFactor(float z)
{
Width = (int)(MyBaseWidth * z);
GraphicsPathWithBorder = RoundedCornerRectangle(ClientRectangle);
Region = new Region(GraphicsPathWithBorder);
}
internal static GraphicsPath RoundedCornerRectangle(Rectangle r)
{
GraphicsPath path = new GraphicsPath();
float size = 10 * 2F;
path.StartFigure();
path.AddArc(r.X, r.Y,
size, size, 180, 90);
path.AddArc((r.X + (r.Width - size)), r.Y,
size, size, 270, 90);
path.AddArc((r.X + (r.Width - size)), (r.Y + (r.Height - size)),
size, size, 0, 90);
path.AddArc(r.X, (r.Y + (r.Height - size)),
size, size, 90, 90);
path.CloseFigure();
return path;
}
Initial screenshot
Screenshot after using the trackbar
The right side of the yellow border becomes invisible after zooming out, and when zooming in there are multiple yellow borders on the right side.
Update:
The answer Works, but there is a part of the control that goes beyond the border. Screenshot for top-right corner, for curveSize = 20:
and for curveSize = 24:
I suggest a slightly different method to draw the Border and the content of the User Control that should also cure the artifacts generated when the control is redrawn.
When you create a Region for a Control and then you paint the Region as it is, the outer borders of the painting are not anti-aliased: the aliased pixels fall outside the Region. The same effect of course is applied when a border is painted around the bounds of the Region.
Here, I apply a Scale Matrix and a Translate Matrix that scale and move the bounds of the Region on the inside of the outer Region that defines the control's bounds.
The size of the scale and the translate transformations are determined by the Pen size.
More information on the Matrix usage here: Flip the GraphicsPath
In this case, when the borders are painted, the outer, anti-aliased, section of the border is inside the Region bounds and the anti-aliasing is preserved.
The background color of the Control is set to Color.Transparent (a User Control supports color transparency on its own).
I've also added a couple of (non decorated) properties that allow to define the inner Color (the Control's BackColor) and Size and Color of the Border. The rest is more or less what it was before.
Sample results:
using System.Drawing;
using System.Drawing.Drawing2D;
public partial class RoundControl : UserControl
{
private GraphicsPath GraphicsPathWithBorder;
private float MyBaseWidth;
private float m_PenSize = 2f;
private Color m_BorderColor = Color.Yellow;
private Color m_FillColor = Color.Green;
public RoundControl()
{
ResizeRedraw = true;
InitializeComponent();
MyBaseWidth = Width;
}
public float BorderSize
{
get => m_PenSize;
set {
m_PenSize = value;
Invalidate();
}
}
public Color BorderColor
{
get => m_BorderColor;
set {
m_BorderColor = value;
Invalidate();
}
}
public Color FillColor
{
get => m_FillColor;
set {
m_FillColor = value;
Invalidate();
}
}
protected override void OnLayout(LayoutEventArgs e) {
UpdateRegion();
base.OnLayout(e);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
RectangleF rect = GraphicsPathWithBorder.GetBounds();
float scaleX = 1 - ((m_PenSize + 1) / rect.Width);
float scaleY = 1 - ((m_PenSize + 1) / rect.Height);
using (Pen pen = new Pen(m_BorderColor, m_PenSize))
using (Brush brush = new SolidBrush(m_FillColor))
using (Matrix mx = new Matrix(scaleX, 0, 0, scaleY, pen.Width / 2, pen.Width / 2))
{
e.Graphics.Transform = mx;
e.Graphics.FillPath(brush, GraphicsPathWithBorder);
e.Graphics.DrawPath(pen, GraphicsPathWithBorder);
}
base.OnPaint(e);
}
internal void SetZoomFactor(float z) {
int newWidth = (int)(MyBaseWidth * z);
if (newWidth <= (30 + m_PenSize * 2)) return;
Width = newWidth;
UpdateRegion();
}
private void UpdateRegion() {
GraphicsPathWithBorder = RoundedCornerRectangle(ClientRectangle);
Region = new Region(GraphicsPathWithBorder);
Invalidate();
}
private GraphicsPath RoundedCornerRectangle(Rectangle r)
{
GraphicsPath path = new GraphicsPath();
// Fixed curve size since we only scale on X-dimension
// Otherwise, adjust also considering the height
float curveSize = 10 * 2.4F;
path.StartFigure();
path.AddArc(r.X, r.Y, curveSize, curveSize, 180, 90);
path.AddArc(r.Right - curveSize, r.Y, curveSize, curveSize, 270, 90);
path.AddArc(r.Right - curveSize, r.Bottom - curveSize, curveSize, curveSize, 0, 90);
path.AddArc(r.X, r.Bottom - curveSize, curveSize, curveSize, 90, 90);
path.CloseFigure();
return path;
}
}
I want to draw a circle with DrawEllipse on a specified Bitmap, with the same size of the Bitmap, but the result is that the circle appears clipped at the edges.
Why this problem?
Bitmap layer = new Bitmap(80, 80);
using (Graphics g = Graphics.FromImage(layer))
{
using (Pen p = new Pen(Color.Black, 4))
{
g.DrawEllipse(p, new Rectangle(0, 0, layer.Width, layer.Height));
}
}
pictureBox3.Size = new Size(100, 100);
pictureBox3.Image = layer;
By default a Pen has a PenAlignment.Center.
This means that half of its widh will draw outside the bounding rectangle.
You can simply avoid the issue by changing it to PenAlignment.Inset:
using (Pen p = new Pen(Color.Black, 4) { Alignment = PenAlignment.Inset})
{
g.DrawEllipse(p, new Rectangle(0, 0, layer.Width, layer.Height));
}
Update: If you want to turn on smoothing for the Graphics object you will need 1 or 2 extra pixels on both sides of the pen stroke for the anti-aliasing pixels. Using a smaller bounding rectanlge can't be avoided now. But..:
Rectangle rect = new Rectangle(Point.Empty, layer.Size);
rect.Inflate(-1, -1); // or -2
..should do..
I made a box that I drew using Graphics.DrawLine() for each edge. But I would like the box to be able to move around the screen freely, along with changing the size of the edges. The box is created by using a middle point and width and height, then basic math to find the 4 points which the lines connect to. My question is: How can I update the position of the lines? Do I have to clear the graphics and re-draw the lines each frame? Any answers will be helpful. Thank you.
Here is the code I am using:
private void Form1_Paint(object sender, PaintEventArgs e)
{
Point box_Middle = new Point(300, 300);
int boxWidth = 100;
int boxHeight = 200;
Pen boxPen = new Pen(Color.Red, 3);
DrawBox(e, boxPen, box_Middle, boxWidth, boxHeight);
}
void DrawBox (PaintEventArgs e, Pen pen, Point middle, int width, int height)
{
graphics = e.Graphics;
graphics.TextRenderingHint = System.Drawing.
Text.TextRenderingHint.
SingleBitPerPixelGridFit;
// Draw Box
Point topLeft = new Point(middle.X - width / 2, middle.Y - height / 2);
Point topRight = new Point(topLeft.X + width, topLeft.Y);
Point bottomLeft = new Point(topLeft.X, topLeft.Y + height);
Point bottomRight = new Point(topRight.X, bottomLeft.Y);
graphics.DrawLine(pen, topLeft, topRight);
graphics.DrawLine(pen, topLeft, bottomLeft);
graphics.DrawLine(pen, bottomLeft, bottomRight);
graphics.DrawLine(pen, topRight, bottomRight);
}
I'm using the GraphicsPath object to draw text in a rectangle. The rectangle is larger than the text, and I want to draw the text in any of the rectangle's corners, and also at the center of its edges.
The problem I have is that when I draw the path, a border is being left around the source rectangle. I want to be able to eliminate that border and make the text touch its bounding rectangle.
Here is the code I have:
private void Form1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
Rectangle textRect = new Rectangle(100, 100, 150, 150);
Font f = new Font("Arial", 16);
float emSize = f.Height * f.FontFamily.GetCellAscent(f.Style) /
f.FontFamily.GetEmHeight(f.Style);
foreach (StringAlignment lineAlignment in Enum.GetValues(typeof(StringAlignment)))
{
foreach (StringAlignment alignment in Enum.GetValues(typeof(StringAlignment)))
{
StringFormat sf = new StringFormat() { LineAlignment = lineAlignment, Alignment = alignment };
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddString("txt", f.FontFamily, (int)f.Style, emSize, textRect, sf);
RectangleF bounds = gp.GetBounds();
g.FillPath(Brushes.Black, gp);
g.DrawRectangle(Pens.Red, Rectangle.Round(bounds));
}
}
}
g.DrawRectangle(Pens.Blue, textRect);
}
And here is the result:
Basically, I want the red rectangles (and the text they contain) to touch the blue rectangle, and to eliminate the border between them. Also, I need to use the GraphicsPath and not DrawString.
I ended up writing a helper method to calculate the offset of the rectangles and translate the text before drawing it. Here is the method I wrote:
private PointF FixAlignment(RectangleF parentRect, RectangleF childRect,
StringAlignment lineAlignment, StringAlignment alignment)
{
float xOffset = 0;
float yOffset = 0;
switch (lineAlignment)
{
case StringAlignment.Near:
yOffset = parentRect.Top - childRect.Top;
break;
case StringAlignment.Far:
yOffset = parentRect.Bottom - childRect.Bottom;
break;
}
switch (alignment)
{
case StringAlignment.Near:
xOffset = parentRect.Left - childRect.Left;
break;
case StringAlignment.Far:
xOffset = parentRect.Right - childRect.Right;
break;
}
return new PointF(xOffset, yOffset);
}
I used it in the Form1_Paint method like this:
private void Form1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
Rectangle textRect = new Rectangle(100, 100, 150, 150);
Font f = new Font("Arial", 16);
float emSize = f.Height * f.FontFamily.GetCellAscent(f.Style) /
f.FontFamily.GetEmHeight(f.Style);
foreach (StringAlignment lineAlignment in Enum.GetValues(typeof(StringAlignment)))
{
foreach (StringAlignment alignment in Enum.GetValues(typeof(StringAlignment)))
{
StringFormat sf = new StringFormat() { LineAlignment = lineAlignment, Alignment = alignment };
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddString("txt", f.FontFamily, (int)f.Style, emSize, textRect, sf);
RectangleF bounds = gp.GetBounds();
// Calculate the rectangle offset
PointF offset = FixAlignment(textRect, bounds, lineAlignment, alignment);
// Translate using the offset
g.TranslateTransform(offset.X, offset.Y);
g.FillPath(Brushes.Black, gp);
g.DrawRectangle(Pens.Red, Rectangle.Round(bounds));
// Translate back to the original location
g.TranslateTransform(-offset.X, -offset.Y);
}
}
}
g.DrawRectangle(Pens.Blue, textRect);
}
Here is the result:
In C# i have a picturebox. i would like to draw 4 colors. The default will be white, red, green, blue. How do i draw these 4 colors stritched in this picbox? or should i have 4 picbox? in that case how do i set the rgb color?
You need to specify what it is you would specifically like to draw. You can't draw a red - that makes no sense. You can, however, draw a red rectangle at location (0,0) which is 100 pixels tall and 100 wide. I will answer what I can, however.
If you want to set the outline of a shape to a specific color, you would create a Pen object. If you want to fill a shape with a color, however, then you would use a Brush object. Here's an example of how you would draw a rectangle filled with red, and a rectangle outlined in green:
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = e.Graphics;
Brush brush = new SolidBrush(Color.Red);
graphics.FillRectangle(brush, new Rectangle(10, 10, 100, 100));
Pen pen = new Pen(Color.Green);
graphics.DrawRectangle(pen, new Rectangle(5, 5, 100, 100));
}
Add a PictureBox to the form, create an event handler for the paint event, and make it look like this:
private void PictureBox_Paint(object sender, PaintEventArgs e)
{
int width = myPictureBox.ClientSize.Width / 2;
int height = myPictureBox.ClientSize.Height / 2;
Rectangle rect = new Rectangle(0, 0, width, height);
e.Graphics.FillRectangle(Brushes.White, rect);
rect = new Rectangle(width, 0, width, height);
e.Graphics.FillRectangle(Brushes.Red, rect);
rect = new Rectangle(0, height, width, height);
e.Graphics.FillRectangle(Brushes.Green, rect);
rect = new Rectangle(width, height, width, height);
e.Graphics.FillRectangle(Brushes.Blue, rect);
}
This will divide the surface into 4 rectangles and paint each of them in the colors White, Red, Green and Blue.
If you want to use non-predefined colors, then you need to get a Color object from the static method Color.FromArgb().
int r = 100;
int g = 200;
int b = 50;
Color c = Color.FromArgb(r, g, b);
Brush brush = new SolidBrush(c);
//...
Best RegardsOliver Hanappi