Well, I'm coding the OnPaint event for my own control and it is very nescessary for me to make it pixel-accurate.
I've got a little problem with borders of rectangles.
See picture:
removed dead ImageShack link
These two rectangles were drawn with the same location and size parameters, but using different size of the pen. See what happend? When border became larger it has eaten the free space before the rectangle (on the left).
I wonder if there is some kind of property which makes border be drawn inside of the rectangle, so that the distance to rectangle will always be the same. Thanks.
You can do this by specifying PenAlignment
Pen pen = new Pen(Color.Black, 2);
pen.Alignment = PenAlignment.Inset; //<-- this
g.DrawRectangle(pen, rect);
If you want the outer bounds of the rectangle to be constrained in all directions you will need to recalculate it in relation to the pen width:
private void DrawRectangle(Graphics g, Rectangle rect, float penWidth)
{
using (Pen pen = new Pen(SystemColors.ControlDark, penWidth))
{
float shrinkAmount = pen.Width / 2;
g.DrawRectangle(
pen,
rect.X + shrinkAmount, // move half a pen-width to the right
rect.Y + shrinkAmount, // move half a pen-width to the down
rect.Width - penWidth, // shrink width with one pen-width
rect.Height - penWidth); // shrink height with one pen-width
}
}
This isn't a direct answer to the question, but you might want to consider using the ControlPaint.DrawBorder method. You can specify the border style, colour, and various other properties. I also believe it handles adjusting the margins for you.
I guess not... but you may move the drawing position half the pen size to the bottom right
Related
I have wrote this code that will create a rounded corner border around a panel.
public void DrawRoundRect(Graphics g, Pen p, float X, float Y, float width, float height, float radius)
{
GraphicsPath gp = new GraphicsPath();
//Upper-right arc:
gp.AddArc(X + width - (radius * 2), Y, radius * 2, radius * 2, 270, 90);
//Lower-right arc:
gp.AddArc(X + width - (radius * 2), Y + height - (radius * 2), radius * 2, radius * 2, 0, 90);
//Lower-left arc:
gp.AddArc(X, Y + height - (radius * 2), radius * 2, radius * 2, 90, 90);
//Upper-left arc:
gp.AddArc(X, Y, radius * 2, radius * 2, 180, 90);
gp.CloseFigure();
g.DrawPath(p, gp);
gp.Dispose();
}
private void panel_Paint(object sender, PaintEventArgs e)
{
Graphics v = e.Graphics;
DrawRoundRect(v, Pens.White, e.ClipRectangle.Left, e.ClipRectangle.Top, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1, 10);
base.OnPaint(e);
}
It works fine, until it goes off screen and this happens:
Does anyone know what I've done wrong, or how to fix the issue?
You misunderstand what ClipRectangle means.
It doesn't tell you how big the panel is - it tells you which part of the panel to redraw. As you move the form off-screen, it needs to redraw the parts that changed monitors - but not the ones that remain on the original one. So instead of redrawing the whole panel (when ClipRectangle is the whole area of the panel, and your code works), it tells you to only redraw a part of it - and ClipRectangle is much smaller than the panel you're trying to draw.
ClipRectangle is really an optimization feature. If you don't care about that, you can pretty much ignore it completely - just use 0, 0, panel.Width, panel.Height.
You have two problems. First is relying on e.ClipRectangle, that is not the size of the panel. You need to use panel.ClientRectangle instead. That's what causes the smearing when you drag it off the screen and back.
You haven't found the next one yet, it is much more subtle. Panel was designed to be a container control and it optimizes its painting. Redrawing only the parts that get revealed when it is resized. Pretty important, makes resizing a window more bearable. That however will not work well for details like this, the rounded corners will not get properly repainted. Looks like a smearing effect as well, less pronounced than what you have now. It will happen whenever you resize the panel, typically with the Dock or Anchor property.
Proper way to do that is to derive your own class from Panel, set the ResizeRedraw property to true in the constructor. You typically almost always also want to set DoubleBuffered to true to suppress the visible flicker you now have. Or use the panel's Resize event, call panel.Invalidate().
I know that I can draw a filled circle and I can draw many simple and complicated things with a Graphics. But I couldn't get it to draw a single point (not a single pixel).
I'm playing with a Paint program and the user can draw fine but not plot a dot. I can add a dummy point really close or can draw a filled cirlce but sometimes I miss the obvious.
So is there a way to draw a single point with a given Brush or Pen?
And no, of course I don't mean to draw a single Pixel. I want to use the Properties like color and width. Like a DrawLine with only one Point or with the same Point twice. But that renders nothing.
public void DrawPoint(Graphics G, Pen pen, Point point)
{
// add more LineCaps as needed..
int pw2 = (int ) Math.Max(1, pen.Width / 2);
using(var brush = new SolidBrush(pen.Color))
{
if (pen.EndCap == LineCap.Square)
G.FillRectangle(brush, point.X - pw2, point.Y - pw2, pen.Width, pen.Width);
else
G.FillEllipse(brush, point.X - pw2, point.Y - pw2, pen.Width, pen.Width);
}
}
I have a panel called "canvas". It is transparent. So the background is from the form image, which is dark blue. This shows in the panel or canvas.
When I save the canvas to image, it saves the background, but not what I have drawn thereon, my drawline pen is yellow. And I can see it drawing on the panel. But when I save it... there are not yellow lines in the image.
What am I missing? Where are my yellow lines?
I am running this with my timer tick... to get the view to update.
This tracks the position of a CNC type machine. Gives a visual of where
the machine is in relation to Zero.
My ultimate goal, is to have a "viewport" that is zoomable, thus getting it
into a image, for easy resizing, and displaying in a pictureBox, which will
handle the stretched image and center it automatically?
I have read some complex solutions, but I am after the simple ones.
Any help would be appreciated. Sincerely,
private void VMoveNow()//Draw on panel called "canvas".
{
double a = GV.MYa * -1; //Change Direction of Y Drawing.
xc = Convert.ToInt32(GV.MXa) + (canvas.Width / 2);
yc = Convert.ToInt32(a) + (canvas.Height / 2);
g = canvas.CreateGraphics();
g.DrawLine(p, x, y, xc, yc);
x = xc;
y = yc;
g.Dispose();
}
private void SaveBMP()
{
try
{
Bitmap mybmp = new Bitmap(canvas.Width, canvas.Height);
canvas.DrawToBitmap(mybmp, canvas.Bounds);
mybmp.Save("C:\\myimage.bmp");
}
catch
{
return;
}
}
Thanks for looking.
I answered my own problem, after several attempts... I have figured out that I can scale my var's used for this drawings... and the size of the Drawline will be scale as a result.
So I now have scaling of the Drawline drawing, in a panel, with no picture or picture box needed. Does what I wished.
Setting the Pen width to -1 takes care of it resizing.
I'm trying to draw some polygons and lines usinng e.Graphics.DrawPolygon (or DrawLine). But I have a little problem specifying the coordinates where to draw. I am drawing onto a PictureBox using its Paint event. The elements draw correctly relatively to each other (creating the required final picture), but seem always to draw in the upper-left corner of the PictureBox. When creating the points to draw, when I just try to multiply the coordinates, it draws it at the same place but bigger (size is multiplied, instead of location coordinates).
Here is my code:
//some for loop
{
//getting the coordinates
Point toAdd = new Point((int)xCoord, (int)yCoord); // creating the point from originaly a double, here i tried to multiply..
tmpPoints.Add(toAdd); // tmpPoints is a List<Point>
}
points.Add(tmpPoints.ToArray()); //List<Point[]>
drawBuffer = points; //saving to a public List<Point[]>
points.Clear();
this.Invalidate();
here part of the pictureBox1_Paint method:
for (int i = 0; i < drawBuffer.Count; i++)
{
//some other stuff like deciding which color to use, not very important
Brush br = new SolidBrush(polyColor);
e.Graphics.FillPolygon(br, drawBuffer[i]);
brush.Dispose();
}
I have checked using breakpoint, the coordiinates are the same ratio (what was 100 pixels wide is still 100 pixels wide), they are at coordinates like x 3000 and y 1500, but it just draws itself in the upper-left corner. When i multiply the coordinates 3 times (see the code for the place where i multiplied), it draws at the same place but 3 times bigger (doesn't make sense after checking the coords...)
So, my question is - how do I set the location correctly, or is there any other way to do this?
Like this (I know, this is nonsense, just an example)
foreach(Polygon poly in e.Graphics)
{
poly.Location = new Point(poly.Location.X * 2, poly.Location.Y * 2);
}
When you multiply the coordinates of the points, they're scaled around the point (0, 0), the top-left corner of the canvas:
In order to scale it around its center (and I suppose you expected it to work this way), you need to calculate some kind of center of the polygon. For simplicity, it can be even an arithmetic mean of the coordinates, on X and Y axes respectively. If you already have the coordinates of the center, translate the coordinates of every point by a reversed vector made from the center coordinates (this is how it would look like if you drew it after this operation - the polygon's center is in the center of the coordinate system):
Now, do your scaling:
and move it back by the vector of polygon's center coordinates:
when you multiply
poly.Location = new Point(poly.Location.X * 2, poly.Location.Y * 2);
you are doing a stretch operation when you add
poly.Location = new Point(poly.Location.X + 50, poly.Location.Y +50); you are doing a translation operation.
If you want to shift everything without modifying the stored coords then just translate the graphics before drawing:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TranslateTransform(100, 100); // shift the origin somehow
// ... draw the polygons as before ...
}
I drew some circles on the Form1 using GDI+, and the center of the circle is a small red rectangle of Custom Control which is derived from User Control, the BackgroundImage property ofForm1's a bitmap which is also drawn by GDI+ with several colors.
What I want is that when I move the red rectangle(the center of circle) with a mouse, the circle will also move following the red rectangle. Using the MouseDown, MouseMove event I could move the red rectangle smoothly with the mouse.
My problem is how to move the circle corresponding to the red rectangle(the center of circle).
I enabled the double buffering to solve the flicker problem. CircleCenter is an object of Custom Control class(e.g. the red rectangle). GObject is a Grahpics object.
Here is some key codes:
public Form1()
{
InitializeComponent();
this.SetStyle(ControlStyles.DoubleBuffer | //enables double-buffering
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint,
true);
}
Point CCenterPoint = new Point();
private int Diameter = 250;
private void CircleCenterMouseDown(object sender, MouseEventArgs e)
{
CCenterPoint = new Point(-e.X, -e.Y);
}
private void CircleCenterMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Point MousePos = CircleCenter.MousePosition;
MousePos.Offset(CCenterPoint.X, CCenterPoint.Y);
CircleCenter.Location = CircleCenter.Parent.PointToClient(MousePos);
CircleCenter.BringToFront();
CirclePen.Color = Color.Black;
GObject.DrawEllipse(CirclePen, CircleCenter.Left- Diameter/2, CircleCenter.Top - Diameter/2, Diameter, Diameter);
this.Invalidate();
}
}
How to remove the black circle drawn by GDI+ produced in the MouseMove proceed?
I googled several websites and didn't get a satisfied answer. Hope you could give me some hints,Thx!
Well, As I understand from your question, You just need to draw a circle around the red rectangle, This is quite easy.
In the Paint event of the Form, Add the following (assuming that your red rectangle control has the name "CircleCenter" and your Form was named "Form1"):
private void Form1_Paint(object sender, PaintEventArgs e)
{
// get the Graphics object of the form.
Graphics g = e.Graphics;
// create a think red pen for the circle drawing
Pen redPen = new Pen(Brushes.Red, 4.0f);
// drawing the circle
PointF ctrlCenter = GetCenterOfControl(CircleCenter);
g.DrawEllipse(redPen,
ctrlCenter.X - (Diameter / 2),
ctrlCenter.Y - (Diameter / 2),
Diameter, Diameter);
}
//The following little method to calculate the center point
PointF GetCenterOfControl(Control ctrl)
{
return new PointF(ctrl.Left + (ctrl.Width / 2),
ctrl.Top + (ctrl.Height / 2));
}
Any way, I know it looks long for such a simple task like Circle drawing! here is the ugly one line version of the above code:
e.Graphics.DrawEllipse(new Pen(Brushes.Red, 4.0f), (centerCircle.Left + (centerCircle.Width / 2)) - (Diameter / 2), (centerCircle.Top + (centerCircle.Height / 2)) - (Diameter / 2), Diameter, Diameter);
You will need to always reset the entire GObject (erase the images you drawn on it) and then redraw them all again.
This can be done by simply drawing a rectangle with the color of the object from which you obtained the Graphics object (although you didn't mentioned it, I think GObject is a Graphics object, obtained of some win control?).
So something like:
Control control = CircleCenter.Parent; // Parent control where you get Graphics object from.
System.Drawing.SolidBrush sBrush = new System.Drawing.SolidBrush(control.BackColor); // Brush to be used with the same color like the one of the parent control.
GObject.FilledRectangle(sBrush, new Rectangle(0, 0, control.Width, control.Height); // Erase background.
GObject.DrawEllipse(CirclePen, CircleCenter.Left- Diameter/2, CircleCenter.Top - Diameter/2, Diameter, Diameter); // Do your stuff.
should hide the old drawings and re-draw the circle on the new location.