kuskus
kuskus

Reputation: 23

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

I recently started programming in C# obviously and was trying to do a simple WinForms app that takes mouse coordinates and scales a Rectangle according to the coordinates.

The issue I am facing is that I don't know how to call a method that uses more arguments (in this case is x, y and PaintEventArgs). Or, I do know what to do with the PaintEvent.

Here is the whole code, since its pretty short and rather simple:

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

I'm trying to call the DrawRect() method to draw the Rectangle with width and height according to the mouse coordinates.

So how can I call the DrawRect() with coordinates and PaintEventArgs?

Upvotes: 1

Views: 3758

Answers (4)

Jimi
Jimi

Reputation: 32223

When drawing on a Control's surface, you always use the Paint event of that Control or override the OnPaint method of a Custom/User Control.
Do not try to store its Graphics object: it becomes invalid as soon as the Control is invalidated (repainted).
Use the Graphics object provided by the PaintEventArgs object.

When a more complex procedure is required to draw different shapes, you can pass the e.Graphics object to different methods which will use this object to perform specialized drawings.


In the example, the coordinates and other properties of each drawn shape are assigned to a specialized class, DrawingRectangle (a simplified structure here, it can hold more complex functionalities).
A List<DrawingRectangle>() stores the references of all the DrawingRectangle objects generated in a session (until the drawing is cleared).

Each time a Left MouseDown event is generated on the Control's surface, a new DrawingRectangle object is added to the List.
The e.Location is stored both as the DrawingRectangle.StartPoint (a value that doesn't change) and the DrawingRectangle.Location: this value will be updated when the mouse pointer is moved.

When the Mouse is moved, the current e.Location value is subtracted from the starting point coordinates previously stored. A simple calculation allows to draw the shapes from all sides.
This measure determines the current Size of the Rectangle.

To remove a Rectangle from the drawing, you just need to remove its reference from the List and Invalidate() the Control that provides the drawing surface.
To clear the drawing surface, clear the List<DrawingRectangle>() (drawingRects.Clear()) and call Invalidate().

Some other examples here:
Transparent Overlapping Circular Progress Bars
GraphicsPath and Matrix classes
Connecting different shapes
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();
}

Drawing Rectangles

Upvotes: 11

0xTheOldOne
0xTheOldOne

Reputation: 65

The PaintEventArgs allows you to access to the Graphics object, you need that one to draw something.

If you don't want to use the PaintEventArgs, i suggest that you call the CreateGraphics() method of your Form, and it will allow you to draw the rectangle.

To improve performance, i suggest that you use the using(...){ } keywork in order to dispose the Graphics object and the Pen object.

You need to include System.Drawing in order to use Graphics and Pen.

You're code will look like that :

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

Upvotes: 0

rs232
rs232

Reputation: 1317

Drawing in WinForms application works in a slightly different way then you probably expect. Everything on screen now is considered to be temporary, if you e.g. minimize and restore your window, the onscreen stuff will be erased and you'll be asked to repaint it again (your window's Paint event will be fired by the system).

That's why that DrawRect method expects PaintEventArgs argument: it is supposed to be called only withing your Paint event handler. If you call it from outside (like it is suggested in other answer) your rectangles might behave inconsistently.

I would suggest remember your rectangles in some internal variable and then repaint them when asked for that by the system:

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

Upvotes: 0

Sinatr
Sinatr

Reputation: 21999

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

I'd expect to see something like this (pseudocode):

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
}

It's pretty simply. Painting is a combination of Invalidate() calls, which rise up paint event. The variables you pass using fields.

Upvotes: 1

Related Questions