How to draw rectangle on MouseDown/Move c#
Solution 1
Some code to go with Ed's correct answer:
Point startPos; // mouse-down position
Point currentPos; // current mouse position
bool drawing; // busy drawing
List<Rectangle> rectangles = new List<Rectangle>(); // previous rectangles
private Rectangle getRectangle() {
return new Rectangle(
Math.Min(startPos.X, currentPos.X),
Math.Min(startPos.Y, currentPos.Y),
Math.Abs(startPos.X - currentPos.X),
Math.Abs(startPos.Y - currentPos.Y));
}
private void canevas_MouseDown(object sender, MouseEventArgs e) {
currentPos = startPos = e.Location;
drawing = true;
}
private void canevas_MouseMove(object sender, MouseEventArgs e) {
currentPos = e.Location;
if (drawing) canevas.Invalidate();
}
private void canevas_MouseUp(object sender, MouseEventArgs e) {
if (drawing) {
drawing = false;
var rc = getRectangle();
if (rc.Width > 0 && rc.Height > 0) rectangles.Add(rc);
canevas.Invalidate();
}
}
private void canevas_Paint(object sender, PaintEventArgs e) {
if (rectangles.Count > 0) e.Graphics.DrawRectangles(Pens.Black, rectangles.ToArray());
if (drawing) e.Graphics.DrawRectangle(Pens.Red, getRectangle());
}
To get a 'canevas' that has double-buffering turned on, so it painting doesn't flicker, use Project + Add New Item, select "Class" and paste this code:
using System;
using System.Windows.Forms;
class Canvas : Panel {
public Canvas() {
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
}
Compile. Drag the new control from the top of the toolbox onto your form, replacing the original 'canevas'. Update the event handlers accordingly.
Solution 2
Don't call CreateGraphics. In MouseDown, store the starting position and a flag to indicate that you are drawing. In MouseMove, check the flag. If you are drawing, create the rectangle relative to the starting position and store it (which you are already doing), and then call Invalidate(). All of your drawing code will be in OnPaint() (canvas.Paint from outside the class, though I would probably create my own class for this to avoid littering your form code with this stuff).
Drawing should be done in your paint handler (OnPaint). If you draw outside of that, your graphics object is not cleared (hence the multiple rectangles) and anything you draw to it can/will be wiped out at seemingly odd times when your window receives a WM_PAINT message.
EDIT: Now that you are having performance problems, there are a couple of simple ways to optimize the painting a bit. First, Invalidate will optionally take a Rectangle as its argument so that you don't have to repaint the entire control. Secondly, if you are drawing on MouseMove you are going to be drawing quite a bit. Using double buffering will help a lot too, just set the DoubleBuffered property to true or add it to the ControlStyles value by calling SetStyle on the control.
Burnzy
Updated on July 22, 2022Comments
-
Burnzy almost 2 years
I am not quite sure how to draw a Rectangle (not filled) when I drag my mousedown while left clicking the mouse.
I have this so far
private void canevas_MouseDown( object sender , MouseEventArgs e ) { if( e.Button == MouseButtons.Left ) { _topLeft = new Point( e.X , e.Y ); _drawing = true; } } private void canevas_MouseMove( object sender , MouseEventArgs e ) { if( _drawing ) { Rectangle rec = new Rectangle( _topLeft.X , _topLeft.Y , ( e.X - _topLeft.X ) , ( e.Y - _topLeft.Y ) ); canevas.CreateGraphics().DrawRectangle( Pens.Black , rec ); } }
But the problems it that I dont want all the rectangles to show up
-
Burnzy over 13 yearsbtw, I am not quite sure what is invalidate for
-
user1703401 over 13 yearsDon't use CreateGraphics(). Use e.Graphics in the Paint event.
-
Pop Catalin over 13 years@Burnzy, Invalidate innvalidates the graphics of the control so that the paint operation is invoked again by windows. (Basically once the control is invalidated it needs to redraw itself)
-
Burnzy over 13 yearsthanks, I had it correctly but it helped me with the reverted drag. :)
-
Burnzy over 13 yearsEverything seems ok so far, except the flickering, any ideas?
-
Burnzy over 13 yearsany idea how to fix the flickering issue?
-
Ed S. over 13 yearsI added some tips. Also realize that, if you do call CreateGraphics (which you should not be doing here anyway), you should also be calling Dispose() on the returned Graphics object.
-
user1703401 over 13 yearsI left a comment, are you using e.Graphics in the Paint event? I tested this code, it is flicker-free even without double-buffering.
-
Burnzy over 13 yearsyes i am, however, the flickering might depend on the background. It wont flicker if its a play gray, but otherwise it does. EDIT nvm, it still flickers even when background is plain simple
-
user1703401 over 13 yearsWhat the heck is a "play gray"? And what's in the background?
-
Burnzy over 13 yearsLol, sorry I meant plain gray. The background is a MandelBrot fractal, however, it still flickers if there is no background.
-
Burnzy over 13 yearsThanks for the tip, but why should I do that?
-
Ed S. over 13 yearsBecause the Graphics class implements IDisposable, which tells you as the client that it maintains references to native resources and that it needs to release them before going out of scope. Relying on the finalizer is non-deterministic, so it's best to wrap the declaration/assignment in a 'using' block.
-
Luron about 13 yearsisn't it supposed to be Math.Max(startPos.Y, currentPos.Y)???? because you are trying to get the upper lefthand corner. making it the larger y variable
-
Max Truxa almost 11 years@Luron: Screen coordinates start in the left upper corner, hence the smaller Y is the nearer it is to the top of the screen. Ergo: The example is correct.