10

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
  • 103
  • 1
  • 1
  • 4

4 Answers4

23

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    thanks, I had it correctly but it helped me with the reverted drag. :) – Burnzy Oct 30 '10 at 20:50
  • Everything seems ok so far, except the flickering, any ideas? – Burnzy Oct 30 '10 at 20:51
  • 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. – Hans Passant Oct 30 '10 at 21:11
  • 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 – Burnzy Oct 30 '10 at 21:33
  • What the heck is a "play gray"? And what's in the background? – Hans Passant Oct 30 '10 at 21:37
  • Lol, sorry I meant plain gray. The background is a MandelBrot fractal, however, it still flickers if there is no background. – Burnzy Oct 30 '10 at 21:40
  • 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 – Luron Mar 22 '11 at 16:23
  • @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. – Max Truxa Jul 25 '13 at 15:38
5

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.

Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • btw, I am not quite sure what is invalidate for – Burnzy Oct 30 '10 at 20:31
  • @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) – Pop Catalin Oct 30 '10 at 20:49
  • any idea how to fix the flickering issue? – Burnzy Oct 30 '10 at 20:54
  • 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. – Ed S. Oct 30 '10 at 20:59
  • Thanks for the tip, but why should I do that? – Burnzy Oct 30 '10 at 23:15
  • 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. – Ed S. Oct 30 '10 at 23:35
0

Alright now I have this, its working but, it flickers alot, even with double buffering.

            private void canevas_MouseDown( object sender , MouseEventArgs e )
            {
                    _topLeft = new Point( e.X , e.Y );
                    if( e.Button == MouseButtons.Left )
                    {
                            _topLeft = new Point( e.X , e.Y );
                            _drawing = true;
                    }
            }

            private void canevas_MouseUp( object sender , MouseEventArgs e )
            {
                    _drawing = false;
                    _bottomRight = new Point( e.X , e.Y );
                    int newX = _topLeft.X - (_bottomRight.X - _topLeft.X) / 2;
                    int newY =_topLeft.Y + (_bottomRight.Y - _topLeft.Y) / 2;
                    MouseEventArgs me = new MouseEventArgs( MouseButtons.Left , 1 , newX , newY , 0 );

                    canevas_MouseClick( canevas , me );
            }

            private void canevas_MouseMove( object sender , MouseEventArgs e )
            {
                    if( _drawing )
                    {
                            _bottomRight = new Point( e.X , e.Y );
                            canevas.Invalidate();
                    }
            }

And then on paint

            private void canevas_Paint( object sender , PaintEventArgs e )
            {
                     Graphics g = canevas.CreateGraphics();
                     g.DrawImage( _buffer , new Rectangle( 0 , 0 , canevas.Width , canevas.Height ) );
                     g.DrawRectangle( Pens.White , new Rectangle( _topLeft.X , _topLeft.Y , ( _bottomRight.X - _topLeft.X ) , ( _bottomRight.Y - _topLeft.Y ) ) );
            }
Burnzy
  • 103
  • 1
  • 1
  • 4
0
    public Form1()
    {
        InitializeComponent();
        // Use the cross "+" cursor
        this.Cursor = System.Windows.Forms.Cursors.Cross;
        // This will reduce flicker (Recommended)
        this.DoubleBuffered = true;
    }

Add this code to your form .this may reduce flickering!

raghu
  • 431
  • 2
  • 11
  • 23