如何使用 Paint 事件在鼠标坐标处绘制形状

How to use the Paint event to draw shapes at mouse coordinates

显然,我最近开始在 C# 编程,并且正在尝试做一个简单的 WinForms 应用程序,该应用程序采用鼠标坐标并根据坐标缩放矩形。

我面临的问题是我不知道如何调用使用更多参数的方法(在本例中为 xyPaintEventArgs)。或者,我知道如何处理 PaintEvent.

这是完整的代码,因为它非常简短:

using System;
using System.Drawing;
using System.Windows.Forms;

public partial class Form1 : Form
{
    public void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        int x = e.X; 
        int y = e.Y;
        String data = (x.ToString() + " " + y.ToString());
        DrawRect(Something, x, y);
    }

    PaintEventArgs pEventArgs;
    private void Form1_Paint(object sender, PaintEventArgs e)
    {

    }

    public void DrawRect(PaintEventArgs e, int rey, int rex)
    {
        Graphics gr = e.Graphics;
        Pen pen = new Pen(Color.Azure, 4);
        Rectangle rect = new Rectangle(0, 0, rex, rey);
        gr.DrawRectangle(pen, rect);
    }
}

我正在尝试调用 DrawRect() 方法根据鼠标坐标用 widthheight 绘制矩形。

那么我怎样才能用坐标调用 DrawRect()PaintEventArgs

PaintEventArgs 允许您访问 Graphics 对象,您需要那个对象来画东西。

如果您不想使用 PaintEventArgs,我建议您调用 FormCreateGraphics() 方法,它将允许您绘制矩形。

为了提高性能,我建议您使用 using(...){ } 键来处理 Graphics 对象和 Pen 对象。

您需要包含 System.Drawing 才能使用 GraphicsPen

您的代码将如下所示:

using System.Drawing;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp2
{
    public partial class Form1 : Form
    {
        Point _coordinates;

        public Form1()
        {
            this._coordinates = new Point();
            this.InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
        }

        public void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            this._coordinates = new Point(e.X, e.Y);
            this.Invalidate();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            // Don't draw on first Paint event
            if(this._coordinates.X != 0 && this._coordinates.Y != 0)
            {
                this.DrawRect(e);
            }
        }

        public void DrawRect(PaintEventArgs e)
        {
            using (Pen pen = new Pen(Color.Azure, 4))
            {
                Rectangle rect = new Rectangle(0, 0, this._coordinates.X, this._coordinates.Y);
                e.Graphics.DrawRectangle(pen, rect);
            }
        }
    }
}

app that takes mouse coordinates and scales rectangle according to the coordinates

我希望看到这样的东西(伪代码):

Point _point;

void Form1_MouseMove(object sender, MouseEventArgs e)
{
    ... // calculate new coordinates/scale factor/whatever here
    _point = ... ; // store results in fields
    Invalidate(); // this will cause repaint every time you move mouse
}

void Form1_Paint(object sender, PaintEventArgs e)
{
    ... // take values from fields
    e.Graphics.DrawRectangle(pen, rect); // draw
}

非常简单。绘画是 Invalidate() 调用的组合,它引发绘画事件。您使用字段传递的变量。

在 WinForms 应用程序中绘图的方式可能与您预期的略有不同。现在屏幕上的所有内容都被认为是临时的,例如,如果您最小化并恢复你的 window,屏幕上的内容将被删除,你将被要求重新绘制它(你的 window 的 Paint 事件将被系统触发)。

这就是 DrawRect 方法需要 PaintEventArgs 参数的原因:它应该只在您的 Paint 事件处理程序中调用。如果您从外部调用它(就像其他答案中建议的那样),您的矩形可能会表现不一致。

我建议记住一些内部变量中的矩形,然后在系统要求时重新绘制它们:

private Point pointToDrawRect = new Point(0,0);
public void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        int x = e.X;
        int y = e.Y;
        String data = (x.ToString() + " " + y.ToString());
        pointToDrawRect= new Point(x, y);
        Invalidate();
    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
         if(pointToDrawRect.X != 0 || pointToDrawRect.Y != 0)
         {
             DrawRect(e, pointToDrawRect.X, pointToDrawRect.Y);
         }
    }

    public void DrawRect(PaintEventArgs e, int rey, int rex)
    {
            using (Pen pen = new Pen(Color.Azure, 4))
            {
                Rectangle rect = new Rectangle(0, 0, rex, rey);
                e.Graphics.DrawRectangle(pen, rect);
            }
    }

在控件的表面上绘图时,您始终使用该控件的 Paint 事件或覆盖 Custom/User 控件的 OnPaint 方法。
不要试图存储它的 Graphics 对象:一旦 Control 无效(重新绘制)它就变得无效。
使用 PaintEventArgs 对象提供的 Graphics 对象。

当需要更复杂的过程来绘制不同的形状时,您可以将 e.Graphics 对象传递给不同的方法,这些方法将使用该对象来执行专门的绘图。


在这个例子中,每个绘制形状的坐标和其他属性被分配给一个专门的class,DrawingRectangle(这里是一个简化的结构,它可以容纳更复杂的功能)。
List<DrawingRectangle>() 存储在 会话 中生成的所有 DrawingRectangle 对象的引用(直到清除绘图)。

每次在控件的表面上生成 Left MouseDown 事件时,都会将一个新的 DrawingRectangle 对象添加到列表中。
e.Location 存储为 DrawingRectangle.StartPoint(一个不变的值)和 DrawingRectangle.Location:当鼠标指针移动时该值将更新。

当鼠标移动时,当前e.Location值将从先前存储的起点坐标中减去。一个简单的计算允许从各个方面绘制形状。
此度量决定了矩形的当前大小。

要从绘图中删除矩形,您只需从列表中删除它的引用和Invalidate()提供绘图表面的控件。
要清除绘图表面,请清除 List<DrawingRectangle>() (drawingRects.Clear()) 并调用 Invalidate().

这里还有一些其他例子:



Drawing Transparent/Translucent Custom Controls

// Assign the Color used to draw the border of a shape to this Field
Color SelectedColor = Color.LightGreen;
List<DrawingRectangle> drawingRects = new List<DrawingRectangle>();

public class DrawingRectangle
{
    public Rectangle Rect => new Rectangle(Location, Size);
    public Size Size { get; set; }
    public Point Location { get; set; }
    public Control Owner { get; set; }
    public Point StartPosition { get; set; }
    public Color DrawingcColor { get; set; } = Color.LightGreen;
    public float PenSize { get; set; } = 3f;
}

private void form1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button != MouseButtons.Left) return; 
    DrawingRects.Add(new DrawingRectangle() {
        Location = e.Location,
        Size = Size.Empty,
        StartPosition = e.Location,
        Owner = (Control)sender,
        DrawingcColor = SelectedColor // <= Shape's Border Color
    });
}

private void form1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button != MouseButtons.Left) return;
    var dr = DrawingRects[DrawingRects.Count - 1];
    if (e.Y < dr.StartPosition.Y) { dr.Location = new Point(dr.Rect.Location.X, e.Y); }
    if (e.X < dr.StartPosition.X) { dr.Location = new Point(e.X, dr.Rect.Location.Y); }

    dr.Size = new Size(Math.Abs(dr.StartPosition.X - e.X), Math.Abs(dr.StartPosition.Y - e.Y));
    this.Invalidate();
}

private void form1_MouseUp(object sender, MouseEventArgs e)
{
    // The last drawn shape
    var dr = DrawingRects.Last();
    // ListBox used to present the shape coordinates
    lstPoints.Items.Add($"{dr.Location}, {dr.Size}");
}

private void form1_Paint(object sender, PaintEventArgs e)
{
    DrawShapes(e.Graphics);
}

private void DrawShapes(Graphics g)
{
    if (DrawingRects.Count == 0) return;
    g.SmoothingMode = SmoothingMode.AntiAlias;
    foreach (var dr in DrawingRects) {
        using (Pen pen = new Pen(dr.DrawingcColor, dr.PenSize)) {
            g.DrawRectangle(pen, dr.Rect);
        };
    }
}

// A Button used to save the current drawing to a Bitmap
private void btnSave_Click(object sender, EventArgs e)
{
    using (var bitmap = new Bitmap(panCanvas.ClientRectangle.Width, panCanvas.ClientRectangle.Height))
    using (var g = Graphics.FromImage(bitmap)) {
        DrawShapes(g);
        bitmap.Save(@"[Image Path]", ImageFormat.Png);
        // Clone the Bitmap to show a thumbnail
    }
}

// A Button used to clear the current drawing
private void btnClear_Click(object sender, EventArgs e)
{
    drawingRects.Clear();
    this.Invalidate();
}