I have a PictureBox with some graphics drawn, able to zoom by mousewheel. To keep the graphics at the (approximately) same position, not to have to move each time after zooming, I translate the graphics after each zoom. Here is my zooming code:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(pictureBox1.BackColor);
float _step = 1.0f;
if (todo == "zoom out")
{
float step = 0;
if (CurrentRate >= 0.60f) step = 0.05f;
else if (CurrentRate >= 0.40f && CurrentRate < 0.60f) step = 0.025f;
else if (CurrentRate >= 0.05f && CurrentRate < 0.40f) step = 0.0125f;
CurrentRate -= step; // current rate is 1.0 on startup
_step = step;
//pictureBox1.Location = new Point((int)(pictureBox1.Location.X + step * 1500), (int)(pictureBox1.Location.Y + step * 1500));
translateX += step * 10500; //achieved these numbers after few dozens of tries, it actually keeps the graphics at the same position..
translateY += step * 8500;
todo = null;
}
else if (todo == "zoom in")
{
float step = 0;
if (CurrentRate >= 1.80f && CurrentRate <= 1.95f) step = 0.0125f;
else if (CurrentRate >= 0.80f && CurrentRate < 1.80f) step = 0.025f;
else if (CurrentRate >= 0.03f && CurrentRate < 0.80f) step = 0.05f;
CurrentRate += step;
_step = step;
translateX -= step * 10500;
translateY -= step * 8500;
//pictureBox1.Location = new Point((int)(pictureBox1.Location.X - step * 1500), (int)(pictureBox1.Location.Y - step * 1500));
todo = null;
}
e.Graphics.TranslateTransform(translateX, translateY); //move it to keep same position
e.Graphics.ScaleTransform(CurrentRate, CurrentRate); //rescale according to the zoom
//the drawing itself (of everything, also the things mentioned below)
Now, what I am trying to do. The user clicks the picturebox, a small rectangle should be drawn at the click position. When he clicks again, another rectangle is drawn, and the rectangles are connected by a line. And on and on to lets say 50 connected rectangles.
Now, the rectangles connect correctly, but everything is drawn with a horrible offset. I believe this is caused by the translation. So I tried to translate the click coordinates as well:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
//MessageBox.Show("e.Location: " + e.Location.ToString() + "to client e.location: " + PointToClient(e.Location).ToString() + "cursor position: " + Cursor.Position.ToString() + "to client cursor position:" + PointToClient(Cursor.Position).ToString() + "/nto screen cursor position: " + PointToScreen(Cursor.Position).ToString());
if (trackDrawing)
{
Point[] rectanglePos = new Point[1];
rectanglePos[0] = new Point(e.Location.X + (int)(translateX), e.Location.Y + (int)translateY);
drawBuffer.Add(rectanglePos);
drawBuffertype.Add("DRAWTRACKRECTANGLE");
if (trackDrawingBuffer.Count > 0)
{
Point[] linePos = new Point[2];
linePos[0] = trackDrawingBuffer[trackDrawingBuffer.Count - 1];
linePos[1] = new Point(e.Location.X + (int)translateX, e.Location.Y + (int)translateY); ;
drawBuffer.Add(linePos);
drawBuffertype.Add("DRAWTRACKLINE");
}
trackDrawingBuffer.Add(new Point(e.Location.X + (int)translateX, e.Location.Y + (int)translateY));
pictureBox1.Invalidate();
}
//some more unrelated code
But that doesn't work. I have tried also without the translates here at the MouseDown event, but still it draws with offset. I am not quite sure how to describe the behavior properly, so I have done a short vid (about 30s) to explain the offset..
The video
Any ideas? Thank you in advance
**
EDIT
**
Now, after edits done according to the answers, my code looks this:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (trackDrawing)
{
Matrix m = transform.Clone();
m.Invert();
Point[] rectanglePos = new Point[1];
rectanglePos[0] = new Point(e.Location.X - 3, e.Location.Y - 3);
m.TransformPoints(rectanglePos);
drawBuffer.Add(rectanglePos);
drawBuffertype.Add("DRAWTRACKRECTANGLE");
if (trackDrawingBuffer.Count > 0)
{
Point[] linePos = new Point[2];
linePos[0] = trackDrawingBuffer[trackDrawingBuffer.Count - 1];
linePos[1] = new Point(e.Location.X, e.Location.Y );
m.TransformPoints(linePos);
drawBuffer.Add(linePos);
drawBuffertype.Add("DRAWTRACKLINE");
}
trackDrawingBuffer.Add(rectanglePos[0]);
pictureBox1.Invalidate();
}
Now, here the translating part, including the code where I get the matrix offset
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(pictureBox1.BackColor);
transform.Translate(-translateX, -translateY);
float _step = 1.0f;
if (todo == "zoom out")
{
float step = 0;
if (CurrentRate >= 0.60f) step = 0.05f;
else if (CurrentRate >= 0.40f && CurrentRate < 0.60f) step = 0.025f;
else if (CurrentRate >= 0.05f && CurrentRate < 0.40f) step = 0.0125f;
CurrentRate -= step;
_step = step;
translateX += step * 10500;
translateY += step * 8500;
todo = null;
}
else if (todo == "zoom in")
{
float step = 0;
if (CurrentRate >= 1.80f && CurrentRate <= 1.95f) step = 0.0125f;
else if (CurrentRate >= 0.80f && CurrentRate < 1.80f) step = 0.025f;
else if (CurrentRate >= 0.03f && CurrentRate < 0.80f) step = 0.05f;
CurrentRate += step;
_step = step;
//pictureBox1.Scale((1f + step), (1f + step));
translateX -= step * 10500;
translateY -= step * 8500;
todo = null;
}
transform.Translate(translateX, translateY); // transform is the Matrix
e.Graphics.Transform = transform;
e.Graphics.ScaleTransform(CurrentRate, CurrentRate);
and here the drawing itself:
for (int i = 0; i < drawBuffer.Count; i++)
{
//...
else if (drawBuffertype[i].ToUpper().Contains("DRAWTRACKRECTANGLE"))
{
e.Graphics.FillRectangle(new SolidBrush(Color.Red), drawBuffer[i][0].X, drawBuffer[i][0].Y, 6, 6);
}
else if (drawBuffertype[i].ToUpper().Contains("DRAWTRACKLINE"))
{
e.Graphics.DrawLine(new Pen(Color.OrangeRed, 2), drawBuffer[i][0], drawBuffer[i][1]);
}
And still drawing like in the first part of video. I just have to be missing something really basic here...
Not my area of expertise...
...but you can keep a class level Matrix to represent the current state of the "world". You can translate, scale, and/or rotate that Matrix to manipulate the world. Just assign that Matrix to e.Graphics.Transform before drawing everything.
Now, when the user clicks, you can clone that Matrix and Invert() it, allowing you to use its TransformPoints() method. This will convert from the screen coords to the equivalent world coords. Store the converted world coords in a List so you can reuse them in the Paint() event.
Play with this example. Add two buttons to a blank form and wire up their click events to the respective typical method names I've got below. Run it and click a few points on the screen. Now hit the first button to rotate, and/or the second button to zoom in. Now try adding a few more points by clicking some more on the form. Hit the buttons and see what happens. Everything should stay relative to each other (I hope):
public partial class Form1 : Form
{
private Matrix MyMatrix = new Matrix();
private List<Point> Points = new List<Point>();
public Form1()
{
InitializeComponent();
this.WindowState = FormWindowState.Maximized;
this.Shown += new EventHandler(Form1_Shown);
}
void Form1_Shown(object sender, EventArgs e)
{
Point Center = new Point(this.ClientRectangle.Width / 2, this.ClientRectangle.Height / 2);
MyMatrix.Translate(Center.X, Center.Y);
this.MouseDown += new MouseEventHandler(Form1_MouseDown);
this.Paint += new PaintEventHandler(Form1_Paint);
}
void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Transform = MyMatrix;
// draw the origin in the center of the form:
e.Graphics.DrawLine(Pens.Red, new Point(-10, 0), new Point(10, 0));
e.Graphics.DrawLine(Pens.Red, new Point(0, -10), new Point(0, 10));
// draw our stored points (that have already been converted to world coords)
foreach (Point pt in Points)
{
Rectangle rc = new Rectangle(pt, new Size(1, 1));
rc.Inflate(10, 10);
e.Graphics.DrawRectangle(Pens.Black, rc);
}
}
void Form1_MouseDown(object sender, MouseEventArgs e)
{
Matrix m = MyMatrix.Clone();
m.Invert();
Point[] pts = new Point[] {new Point(e.X, e.Y)};
m.TransformPoints(pts);
Points.Add(pts[0]);
this.Refresh();
}
private void button1_Click(object sender, EventArgs e)
{
MyMatrix.Rotate(10);
this.Refresh();
}
private void button2_Click(object sender, EventArgs e)
{
MyMatrix.Scale(1.1f, 1.1f);
this.Refresh();
}
}
Maybe this simple code can help you. This is all done without translation or scaling of any kind. But if you apply scaling to your graphics, it will work the same.
this.pictureBox1.Scale(new SizeF(2.5f, 2.5f));
So...
Actually I defined the side of the rectangle to 15 unit wide.
private int RectSideLen = 15;
So every time I click in the picture box I assume that I click the center of my rectangle to be drawn. This mean that our rectangle will start at the click location minus half rectangle side.
int cornerOffset = RectSideLen / 2;
Point newUpLeftCorner = e.Location;
newUpLeftCorner.Offset(-cornerOffset, -cornerOffset);
Then I add it to a list of rectangle and refresh the picture box to redraw it with the new rectangle added.
pictureBox1.Refresh();
and inside the paint event of the picture box I simply draw the precalculated rectangle.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Red, 1))
{
foreach (Rectangle r in DrawBuffer)
{
e.Graphics.DrawRectangle(pen, r);
}
}
}
So here is the complete sample.
public partial class Form1 : Form
{
private int RectSideLen = 15;
private IList<Rectangle> DrawBuffer = new List<Rectangle>();
public Form1()
{
InitializeComponent();
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
int cornerOffset = RectSideLen / 2;
Point newUpLeftCorner = e.Location;
newUpLeftCorner.Offset(-cornerOffset, -cornerOffset);
DrawBuffer.Add(new Rectangle(newUpLeftCorner, new Size(RectSideLen, RectSideLen)));
pictureBox1.Refresh();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Red, 1))
{
foreach (Rectangle r in DrawBuffer)
{
e.Graphics.DrawRectangle(pen, r);
}
}
}
}
Related
class Cycle : Form1
{
public void Draw(Graphics G,Brush b, float x, float y, float w, float h)
{
G.FillEllipse( b, x, y, w, h);
}
}
This is my Cycle class that inherits from Form1.
List<Point> star = new List<Point>();
List<Point> endd = new List<Point>();
private void Panel1_MouseDown(object sender, MouseEventArgs e)
{
moving = true;
Start = e.Location;
star.Add(e.Location);
}
private void Panel1_MouseMove(object sender, MouseEventArgs e)
{
if (moving)
{
End = e.Location;
Panel1.Invalidate();
}
}
private void Panel1_MouseUp(object sender, MouseEventArgs e)
{
endd.Add(e.Location);
moving = false;
}
These are my mouse event args that keeps locations.
private void Panel1_Paint(object sender, PaintEventArgs e)
{
Cycle c = new Cycle();
if (End.Y > Start.Y && End.X > Start.X)
{
foreach (Point pt in star)
{
foreach(Point p in endd)
c.Draw(e.Graphics, brush, pt.X - (p.X - pt.X) / 2 - (p.Y - pt.Y) / 2, pt.Y - (p.Y - pt.Y) / 2 - (p.X - pt.X) / 2, ((p.X - pt.X) + (p.Y - pt.Y)), ((p.X - pt.X) + (p.Y - pt.Y)));
}
}
}
And this code draws ellipses in panel. However the first ellipse has been drawn perfect but other ellipses got so big that covered all the panel.
I want to keep locations because after drawing 3-4 ellipses I will try to save the points into .txt. After that I will load it to draw again. But there is this problem in drawing :/
I'm currently working on a battleships game,but i've ran into an issue.So far i've managed to draw the grid.the purpose of the draw method is to draw an image(don't know how to/if i can color a certain surface) inside a square in the grid,when i left-click.
the problem here is that,even if the image's size is 25x25(the size of a square) it occupies like half the screen,and that's when it works.50% of the times when i run nothing happens,and the other 50% it draws a huge image in the middle of the screen,regardless of where the cursor is located or if i left-click.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Paint += new PaintEventHandler(form1_paint);
}
private void form1_paint(object sender, PaintEventArgs e)
{
draw(e);
}
int x,y;
private void draw(PaintEventArgs e)
{
if (MouseButtons.Left != 0)
{
x = Cursor.Position.X;
y = Cursor.Position.Y;
Image poza = Image.FromFile("D://C//12E//c#//yellow4.jpg");
if (x < 301 && x > 24 && y < 301 && y > 24)
{
PointF coltz = new PointF(x / 25 * 25, y / 25 * 25);
e.Graphics.DrawImage(poza, coltz);
}
}
}
Does anyone know how i can solve this?or if someone has a better idea for a battleships grid, I am open to suggestions.Thanks!
Fisrt of all, this line of code: Cursor.Position.X gives you the global position of the cursor on the screen, not in the game window. I suggest you to handle MouseMove event to get the position relative to the content of your app.
The second thing is that you are loading the image from file on your computer. I think it's better to add the image to your app's resources, so you can load it easier just calling it by name, e.g.: AppName.Properties.Resources.ImageName - it returns Image object you can immediately use.
One more thing. This if (MouseButtons.Left != 0) won't check whether left mouse button is pressed or not. You have to check if MouseButtons property equals System.Windows.Forms.MouseButtons.Left.
Here's the full code that works for me:
public partial class Form1 : Form
{
private int x, y;
public Form1()
{
InitializeComponent();
Paint += Form1_Paint;
MouseMove += Form1_MouseMove;
MouseDown += Form1_MouseMove;
}
void Form1_Paint(object sender, PaintEventArgs e)
{
Draw(e);
}
private void Draw(PaintEventArgs e)
{
if (MouseButtons == System.Windows.Forms.MouseButtons.Left)
{
if (x < 301 && x > 24 && y < 301 && y > 24)
{
PointF coltz = new PointF(x / 25 * 25, y / 25 * 25);
e.Graphics.DrawImage(AppName.Properties.Resources.ImageName, coltz);
}
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
x = e.X;
y = e.Y;
Invalidate();
}
}
And here's the result:
I also subscribed MouseDown event to show yellow rectangle when user clicks the button without moving the cursor.
the thing is,before adding your code,the draw worked fine.now,every time i move the mouse,the grid gets redrawn,and it looks like it's constantly refreshing,bleeping somehow.not sure how to phrase this
public Form1()
{
InitializeComponent();
Paint += Form1_Paint;
MouseMove += Form1_MouseMove;
MouseDown += Form1_MouseMove;
}
private int x,y;
void Form1_Paint(object sender, PaintEventArgs e)
{
Draw(e);
}
private void Draw(PaintEventArgs e)
{
if (MouseButtons == System.Windows.Forms.MouseButtons.Left)
{
if (x < 301 && x > 24 && y < 301 && y > 24)
{
PointF coltz = new PointF(x / 25 * 25, y / 25 * 25);
e.Graphics.DrawImage(battleships.Properties.Resources.yellow4, coltz);
}
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
x = e.X;
y = e.Y;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g;
g = e.Graphics;
Pen pen = new Pen(Color.Black);
pen.Width = 1;
for (int i = 25; i <= 300; i = i + 25)
{
g.DrawLine(pen, i, 25, i, 300);
g.DrawLine(pen, 25, i, 300, i);
}
}
}
i got the picture box to move. But when i click down on the picture box it jumps all over the place. Can anyone help me with this at all?
namespace Move_Shapes
{
public partial class Form1 : Form
{
int X = 0;
int Y = 0;
int mX = 0;
int mY = 0;
bool Move = false;
public Form1()
{
InitializeComponent();
}
private void pic_TL_Click(object sender, EventArgs e)
{
//X = MousePosition.X - this.Location.X - 8 - pic_TL.Location.X; // loc of cursor in picture
//Y = MousePosition.Y - this.Location.Y - 30 - pic_TL.Location.Y;
//label1.Text = X.ToString();
//label2.Text = Y.ToString();
//X = MousePosition.X - this.Location.X - 8;
//Y = MousePosition.Y - this.Location.Y - 30;
//label3.Text = X.ToString();
//label4.Text = Y.ToString();
}
private void pic_TL_MouseDown(object sender, MouseEventArgs e)
{
X = MousePosition.X - this.Location.X - 8 - pic_TL.Location.X; // loc of cursor in picture
Y = MousePosition.Y - this.Location.Y - 30 - pic_TL.Location.Y;
label1.Text = X.ToString();
label2.Text = Y.ToString();
Move = true;
}
private void pic_TL_MouseMove(object sender, MouseEventArgs e)
{
mX = MousePosition.X - this.Location.X - 8 - pic_TL.Location.X; // loc of cursor in picture
mY = MousePosition.Y - this.Location.Y - 30 - pic_TL.Location.Y;
if (Move)
{
pic_TL.Location = new Point(mX - X, mY - Y);
}
}
private void pic_TL_MouseUp(object sender, MouseEventArgs e)
{
Move = false;
}
}
}
On MoseMove you set location to difference of current mouse position and initial mouse position.
pic_TL.Location = new Point(mX - X, mY - Y);
I case mouse moved over one pixel, picture will move to left-top corner.
I'v been making a program where the user clicks, and wherever they drag their mouse, it draws a line to. It works, but it draws the lines in the wrong place. I think its because I am getting the x y of the whole form, not my panel. Heres the code:
private void panel2_MouseDown(object sender, MouseEventArgs e)
{
vars.x = MousePosition.X;
vars.y = MousePosition.Y;
label1.Text = vars.x + ", " + vars.y;
vars.isDrawing = true;
}
private void panel2_MouseMove(object sender, MouseEventArgs e)
{
if (vars.isDrawing == true)
{
if (e.Button == MouseButtons.Left)
{
int x2 = MousePosition.X;
int y2 = MousePosition.Y;
Random randomGen = new Random();
Color randomColor = Color.FromArgb(randomGen.Next(255), randomGen.Next(255), randomGen.Next(255));
Pen line = new Pen(randomColor, 1);
System.Drawing.Graphics formGraphics = panel2.CreateGraphics();
formGraphics.DrawLine(line, vars.x, vars.y, x2, y2);
}
}
}
Use e.X and e.Y instead of MousePosition.X and MousePosition.Y
I wrote this code:
private struct MovePoint
{
public int X;
public int Y;
}
private void Image_MouseDown(object sender, MouseEventArgs e)
{
FirstPoint = new MovePoint();
FirstPoint.X = e.X;
FirstPoint.Y = e.Y;
}
private void Image_MouseMove(object sender, MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
{
if(FirstPoint.X > e.X)
{
Rectangle.X = FirstPoint.X - e.X;
//Rectangle.Width -= FirstPoint.X - e.X;
} else
{
Rectangle.X = FirstPoint.X + e.X;
//Rectangle.Width += FirstPoint.X + e.X;
}
if(FirstPoint.Y > e.Y)
{
Rectangle.Y = FirstPoint.Y - e.Y;
//Rectangle.Height -= FirstPoint.Y - e.Y;
} else
{
Rectangle.Y = FirstPoint.Y + e.Y;
//Rectangle.Height += FirstPoint.Y + e.Y;
}
Image.Invalidate();
}
}
private void Image_Paint(object sender, PaintEventArgs e)
{
if(Pen != null) e.Graphics.DrawRectangle(Pen, Rectangle);
}
Rectangle moves, but with inversion (it should not be). Can you help?
The mathematics in your mouse-move handler for moving the rectangle based on the mouse-movements seems quite off; I think you want something like this:
private void Image_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int initialX = 0, initialY = 0; // for example.
Rectangle.X = (e.X - FirstPoint.X) + initialX;
Rectangle.Y = (e.Y - FirstPoint.Y) + initialY;
Image.Invalidate();
}
}
This way, the rectangle's upper left corner will follow the mouse by tracking the delta between the initial mouse-down location and the current mouse location. Note however that each time you re-click and drag, the rectangle will move back to its original location.
If, instead, you want the Rectangle to 'remember' its position across multiple click-and-drag operations (i.e. not to be reinitialized to its initial location on mouse-down) you can do:
private void Image_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// Increment rectangle-location by mouse-location delta.
Rectangle.X += e.X - FirstPoint.X;
Rectangle.Y += e.Y - FirstPoint.Y;
// Re-calibrate on each move operation.
FirstPoint = new MovePoint { X = e.X, Y = e.Y };
Image.Invalidate();
}
}
One other suggestion: There's no need to create your own MovePoint type when there's already the System.Drawing.Point type. Also, in general, try not to create mutable structs.