How to draw rectangle on MouseDown/Move c#

29,882

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.

Share:
29,882
Burnzy
Author by

Burnzy

Updated on July 22, 2022

Comments

  • Burnzy
    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
    Burnzy over 13 years
    btw, I am not quite sure what is invalidate for
  • user1703401
    user1703401 over 13 years
    Don't use CreateGraphics(). Use e.Graphics in the Paint event.
  • Pop Catalin
    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
    Burnzy over 13 years
    thanks, I had it correctly but it helped me with the reverted drag. :)
  • Burnzy
    Burnzy over 13 years
    Everything seems ok so far, except the flickering, any ideas?
  • Burnzy
    Burnzy over 13 years
    any idea how to fix the flickering issue?
  • Ed S.
    Ed S. over 13 years
    I 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
    user1703401 over 13 years
    I 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
    Burnzy over 13 years
    yes 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
    user1703401 over 13 years
    What the heck is a "play gray"? And what's in the background?
  • Burnzy
    Burnzy over 13 years
    Lol, sorry I meant plain gray. The background is a MandelBrot fractal, however, it still flickers if there is no background.
  • Burnzy
    Burnzy over 13 years
    Thanks for the tip, but why should I do that?
  • Ed S.
    Ed S. over 13 years
    Because 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
    Luron about 13 years
    isn'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
    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.