1

How can I draw a rectangle in all directions? Current code:

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    // Starting point of the selection:
    if (e.Button == MouseButtons.Left)
    {
        _selecting = true;
        _selection = new Rectangle(new Point(e.X, e.Y), new Size());
    }
}

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    // Update the actual size of the selection:
    if (_selecting)
    {
        _selection.Width = e.X - _selection.X;
        _selection.Height = e.Y - _selection.Y;
        pictureBox1.Refresh(); // redraw picturebox

    }
}

private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left && _selecting)
    {
        _selecting = false;
    }
}

This allows me to MouseDown and then draw down and to the right, but I am unable to draw in any other direction beyond the anchor point. How can I draw a rectangle in any direction like e.g. Microsoft Paint?

enter image description here

I first tried:

_selection.Width = Math.Abs(e.X - _selection.X);
_selection.Height = Math.Abs(e.Y - _selection.Y);

But this creates a funny mirror effect (which is not what I want). I then tried a simple shift:

_selection.X = _selection.Left - 5;

This did what I expected and moved a static rectangle 5 units left, so I thought it would be a simple matter of continuously shifting the anchor point during the Paint event:

private void UpdateRectange(Point newPos)
{
    var width = newPos.X - _selection.X;
    var height = newPos.Y - _selection.Y;
    _selection.Width = Math.Abs(width);
    _selection.Height = Math.Abs(height);
    if (width < 0 && height > 0) // move down (+) and left (-)
    {
        //_selection.X = _selection.Left + width;
        _selection.Offset(width, 0);
    }
    uxScreenGrab.Refresh(); // redraw picturebox
}

But this resulted in pushing a vertical line across the screen towards the left, when moving to the left of the original anchor point.The width does not properly update for some reason.

enter image description here

James Z
  • 12,209
  • 10
  • 24
  • 44
ChE Junkie
  • 326
  • 2
  • 9
  • I added an answer. But what do You mean with "drag" a rectangle. You're changing the width of the rectangle not moving it around the screen, like shifting it. – Claudio Ferraro Feb 02 '20 at 02:00
  • I meant mouse-down and drag to expand the rectangle. I will update shortly. Thank you for your input below, it helped me solve the issue which was silly obvious in hindsight i.e. keep track of the starting point so that I can update the location of the rectangle. – ChE Junkie Feb 02 '20 at 02:08
  • 1
    ok. glad it helped. If You think this is the best solution, just mark it as best answer on the arrow below the up vote. otherwise provide the best answer You found for other users. – Claudio Ferraro Feb 02 '20 at 02:11
  • 1
    Take a look at the methods shown [here](https://stackoverflow.com/a/53708936/7444103), to handle multiple shapes. It may came in handy. – Jimi Feb 03 '20 at 00:51

3 Answers3

2

The solution proposed in the question looks good:

_selection.Width = Math.Abs(e.X - initiallySelectedX);
_selection.Height = Math.Abs(e.Y - initiallySelectedY);

...but You have to move the origin of the rectangle when (e.X - initiallySelectedX) < 0

So probably You want to add something like this to Your code:

var diffX = e.x - initiallySelectedX;
if (diffX < 0) _selection.X = initiallySelectedX - diffX;
var diffY = e.y - initiallySelectedY;
if (diffY < 0) _selection.Y = initiallySelectedY - diffY;

Where initiallySelectedX and initiallySelectedY are variables set onMouseDown. this is only a rough idea. The idea behind is that the Width and Height of the Rectangle cannot be NEGATIVE !!

Claudio Ferraro
  • 4,551
  • 6
  • 43
  • 78
2

Here's another snippet to draw a selection rectangle on a PictureBox:

//...
private Point startPoint;
private Point endPoint;
private readonly Pen P1 = new Pen(Color.SteelBlue, 2) {
    Alignment = PenAlignment.Center, DashStyle = DashStyle.Dash};
//...

Set the startPoint in the MouseDown event and reset the endPoint variables:

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        startPoint = new Point(e.X, e.Y);
        endPoint = Point.Empty;
    }
}

Set the endPoint and call Invalidate() method in the MouseMove event:

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    var p = new Point(e.X, e.Y);

    if (e.Button == MouseButtons.Left &&
        !p.Equals(startPoint))
    {
        endPoint = p;
        pictureBox1.Invalidate();
    }
}

Also call Invalidate() in the MouseUp event to remove the selection rectangle:

private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
    pictureBox1.Invalidate();
}

Draw the selection rectangle:

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    if(MouseButtons == MouseButtons.Left &&
        !startPoint.Equals(endPoint) &&
        !endPoint.Equals(Point.Empty))
    {
        var g = e.Graphics;
        var rect = Rectangle.Empty;

        if(startPoint.X < endPoint.X)
        {
            rect.X = startPoint.X;
            rect.Width = endPoint.X - startPoint.X;
        }
        else
        {
            rect.X = endPoint.X;
            rect.Width = startPoint.X - endPoint.X;
        }
        if(startPoint.Y < endPoint.Y)
        {
            rect.Y = startPoint.Y;
            rect.Height = endPoint.Y - startPoint.Y;
        }
        else
        {
            rect.Y = endPoint.Y;
            rect.Height = startPoint.Y - endPoint.Y;
        }
        g.DrawRectangle(P1, rect);
    }
}

And don't forget to clean up:

private void YourForm_FormClosing(object sender, FormClosingEventArgs e)
{
    P1.Dispose();
}

Keep it simple.

SOQ60022534

Related Posts

How to call a method that uses PaintEventArgs and coordinates variables

  • Thank you. That reads a lot nicer than my spaghetti :-) Quick question: In the `MouseMove` event I used `e.Location` instead of creating a new `Point`. I assume this is okay? (I am just a beginner.) – ChE Junkie Feb 02 '20 at 04:05
  • 1
    @ChEJunkie I love spaghetti. Yes No problem. –  Feb 02 '20 at 04:09
  • 1
    `var rect = new Rectangle(Math.Min(startPoint.X,endPoint.X), Math.Min(startPoint.Y,endPoint.Y),Math.Abs(endPoint.X-startPoint.X),Math.Abs(endPoint.Y-startPoint.Y));` **This one line can replace all your `if` statements**. Better yet, use `using static System.Math;` to remove all `Math.` qualifications. – John Alexiou Feb 02 '20 at 06:14
  • @ja72 Thank you mate for fixing my spaghetti code this time. Hope you love it. (I mean the spaghetti) :) –  Feb 02 '20 at 06:17
  • 1
    @JQSOFT - sure do. – John Alexiou Feb 02 '20 at 07:06
1

UPDATE:

Keeping track of the starting point on the MouseDown event allows the anchor point to correctly update:

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    // Starting point of the selection:
    if (e.Button == MouseButtons.Left)
    {
        _selecting = true;
        _selection = new Rectangle(new Point(e.X, e.Y), new Size());
        _startingPoint = e.Location;
    }
}

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    // Update the actual size of the selection:
    if (_selecting)
    {
        UpdateRectange(e.Location);
    }
}

private void UpdateRectange(Point newPos)
{
    var diffX = newPos.X - _startingPoint.X;
    var diffY = newPos.Y - _startingPoint.Y;
    var newSize = new Size(Math.Abs(diffX), Math.Abs(diffY));
    if (diffX > 0 && diffY > 0)
    {
        _selection = new Rectangle(_startingPoint, newSize);
    }
    else if (diffX < 0 && diffY < 0)
    {
        _selection = new Rectangle(newPos, newSize);
    }
    else if (diffX > 0 && diffY < 0)
    {
        _selection = new Rectangle(new Point(_startingPoint.X, _startingPoint.Y + diffY), newSize);
    }
    else
    {
        _selection = new Rectangle(new Point(_startingPoint.X + diffX, _startingPoint.Y), newSize);
    }
    uxScreenGrab.Invalidate();
}

UPDATE 2:

Re-wrote UpdateRectangle to simply move the anchor point, instead of instantiating at every call:

private void UpdateRectange(Point newPos)
{
    var diffX = newPos.X - _startingPoint.X;
    var diffY = newPos.Y - _startingPoint.Y;
    var newSize = new Size(Math.Abs(diffX), Math.Abs(diffY));
    if (diffX > 0 && diffY > 0)
    {
        _selection.Size = newSize;
    }
    else if (diffX < 0 && diffY < 0)
    {
        _selection.Location = newPos;
        _selection.Size = newSize;
    }
    else if (diffX > 0 && diffY < 0)
    {
        _selection.Y = _startingPoint.Y + diffY;
        _selection.Size = newSize;
    }
    else
    {
        _selection.X = _startingPoint.X + diffX;
        _selection.Size = newSize;
    }
    uxScreenGrab.Invalidate();
}
ChE Junkie
  • 326
  • 2
  • 9
  • 2
    I like it but in Your place I would suggest You to not initialize so much : new Rectangle objects . The UpdateRectangle function could be called dozens of times per second onMouseMove and allocate so much Rectangle objects could be pretty expensive in terms of memory resources and CPU because every time a bunch of Rectancle properties and methods are allocated in memory. Keep it simplier. Optimize it. – Claudio Ferraro Feb 02 '20 at 02:29