1

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...

4

2 回答 2

1

不是我擅长的领域...

...但是您可以保留一个类级别的矩阵来表示“世界”的当前状态。您可以平移、缩放和/或旋转该矩阵来操纵世界。e.Graphics.Transform只需在绘制所有内容之前将该矩阵分配给即可。

现在,当用户单击时,您可以克隆该 Matrix 并 Invert() 它,允许您使用它的 TransformPoints() 方法。这将从屏幕坐标转换为等效的世界坐标。将转换后的世界坐标存储在 List 中,以便您可以在 Paint() 事件中重复使用它们。

玩这个例子。将两个按钮添加到一个空白表单,并将它们的点击事件连接到我在下面得到的各自的典型方法名称。运行它并单击屏幕上的几个点。现在点击第一个按钮旋转,和/或第二个按钮放大。现在尝试通过单击表单上的更多点来添加更多点。点击按钮,看看会发生什么。一切都应该保持相对(我希望):

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();
    }

}

通过 Matrix 屏幕显示世界坐标

于 2013-10-10T19:02:35.677 回答
1

也许这个简单的代码可以帮助你。这一切都是在没有任何形式的平移或缩放的情况下完成的。但是如果你对你的图形应用缩放,它的工作原理是一样的。

this.pictureBox1.Scale(new SizeF(2.5f, 2.5f));

所以...

实际上,我将矩形的边定义为 15 个单位宽。

private int RectSideLen = 15;

所以每次我点击图片框时,我都会假设我点击了要绘制的矩形的中心。这意味着我们的矩形将从点击位置减去半个矩形边开始。

int cornerOffset = RectSideLen / 2;
Point newUpLeftCorner = e.Location;
newUpLeftCorner.Offset(-cornerOffset, -cornerOffset);

然后我将它添加到矩形列表中并刷新图片框以使用添加的新矩形重新绘制它。

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);
        }
    }
}

所以这里是完整的示例。

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);
            }
        }
    }
}
于 2013-10-10T18:12:28.900 回答