Search code examples
c#winformsdrawingshapes

How to make a shape selectable within WinForm


I have a c# WinForm project with the following code to draw Rectangles and Ellipses:

public partial class Form1 : Form
{

    List<Rectangle> _rectangles = new();
    List<Rectangle> _ellipses = new();
    Rectangle _rectInProgress;

    bool DrawingRectangle = false;
    bool DrawingEllipse = false;

    public Form1()
    {
        InitializeComponent();
        DoubleBuffered = true;
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

    private void Form1_MouseDown(object sender, MouseEventArgs e)
    {
        if (DrawingRectangle || DrawingEllipse)
        {
            _rectInProgress = new Rectangle(e.Location, new Size());
        }
    }

    private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        if (MouseButtons == MouseButtons.Left && (DrawingRectangle || DrawingEllipse))
        {
            _rectInProgress.Width = e.Location.X - _rectInProgress.X;
            _rectInProgress.Height = e.Location.Y - _rectInProgress.Y;
            Invalidate();
        }
    }

    private void Form1_MouseUp(object sender, MouseEventArgs e)
    {
        if (DrawingRectangle)
        {
            _rectangles.Add(_rectInProgress);
            Invalidate();
        }
        else if (DrawingEllipse)
        {
            _ellipses.Add(_rectInProgress);
            Invalidate();
        }
    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {

        if (_rectangles.Any())
        {
            e.Graphics.DrawRectangles(new Pen(Color.Blue, 3), _rectangles.ToArray());
        }
        if (_ellipses.Any())
        {
            foreach (var ell in _ellipses)
            {
                e.Graphics.DrawEllipse(new Pen(Color.Blue, 3), ell);
            }
        }
        if (MouseButtons == MouseButtons.Left)
        {
            if (DrawingEllipse)
            {
                e.Graphics.DrawEllipse(new Pen(Color.Red, 3), _rectInProgress);
            }
            else if (DrawingRectangle)
            {
                e.Graphics.DrawRectangle(new Pen(Color.Red, 3), _rectInProgress);
            }
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        DrawingRectangle = !DrawingRectangle;
        DrawingEllipse = false;
    }

    private void button2_Click(object sender, EventArgs e)
    {
        DrawingEllipse = !DrawingEllipse;
        DrawingRectangle = false;
    }
}

The next step is to make the shapes selectable, so they can be resized. I am new to this kind of thing, so I have no idea where to start. I found many examples on the internet, but they all have one thing in common: they check if the cursor clicked WITHIN the shape. I need to check if the cursor clicked ON the shape (the borders).

Anyone any tips on how to proceed? Thanks in advance!


Solution

  • If this variant is interesting, I can think about ellipses.

    public partial class Form1 : Form
    {
        [Flags]
        enum Captures { None = 0, Left = 1, Top = 2, Right = 4, Bottom = 8 }
    
        List<Rectangle> _rectangles = new();
        List<Rectangle> _ellipses = new();
        Rectangle _rectInProgress;
    
        bool DrawingRectangle = false;
        bool DrawingEllipse = false;
    
        Captures capture;
        int captIndex;
        Size captDelta;
        Rectangle captRect;
        const double CAPTURE_DIST = 16;
        Point startLocation;
    
        private Size Delta(Point p, int x, int y) => new(p.X - x, p.Y - y);
        private double Dist(Size s) => Math.Sqrt(s.Width * s.Width + s.Height * s.Height);
    
        private void setSize(Point p)
        {
            var r = captRect;
    
            var left = r.Left;
            var top = r.Top;
            var right = r.Right;
            var bottom = r.Bottom;
    
            if (capture.HasFlag(Captures.Left)) left = p.X - captDelta.Width;
            if (capture.HasFlag(Captures.Top)) top = p.Y - captDelta.Height;
            if (capture.HasFlag(Captures.Right)) right = p.X - captDelta.Width;
            if (capture.HasFlag(Captures.Bottom)) bottom = p.Y - captDelta.Height;
    
            if (left > right) { var b = left; left = right; right = b; }
            if (top > bottom) { var b = top; top = bottom; bottom = b; }
    
            _rectangles[captIndex] = new(left, top, right - left, bottom - top);
        }
    
        private void setRectInProgress(Point p)
        {
            int left, right;
            if (startLocation.X < p.X)
            { left = startLocation.X; right = p.X; }
            else { right = startLocation.X; left = p.X; }
    
            int top, bottom;
            if (startLocation.Y < p.Y)
            { top = startLocation.Y; bottom = p.Y; }
            else { bottom = startLocation.Y; top = p.Y; }
    
            _rectInProgress = new(left, top, right - left, bottom - top);
        }
    
        public Form1()
        {
            InitializeComponent();
            DoubleBuffered = true;
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
    
        }
    
        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            var minDelta = Size.Empty;
            var minDist = CAPTURE_DIST;
            var minIndex = -1;
            var minCapture = Captures.None;
    
            for (var ri = 0; ri < _rectangles.Count; ++ri)
            {
                var r = _rectangles[ri];
    
                var captNode = false;
                var delta = Delta(e.Location, r.Left, r.Top);
                var dist = Dist(delta);
                if (minDist > dist) { minCapture = Captures.Left | Captures.Top; minDelta = delta; minDist = dist; minIndex = ri; captNode = true; }
    
                delta = Delta(e.Location, r.Right, r.Top);
                dist = Dist(delta);
                if (minDist > dist) { minCapture = Captures.Right | Captures.Top; minDelta = delta; minDist = dist; minIndex = ri; captNode = true; }
    
                delta = Delta(e.Location, r.Left, r.Bottom);
                dist = Dist(delta);
                if (minDist > dist) { minCapture = Captures.Left | Captures.Bottom; minDelta = delta; minDist = dist; minIndex = ri; captNode = true; }
    
                delta = Delta(e.Location, r.Right, r.Bottom);
                dist = Dist(delta);
                if (minDist > dist) { minCapture = Captures.Right | Captures.Bottom; minDelta = delta; minDist = dist; minIndex = ri; captNode = true; }
    
                if (captNode) continue;
    
                if (r.Left < e.Location.X && e.Location.X < r.Right)
                {
                    delta.Height = e.Location.Y - r.Top;
                    dist = Math.Abs(delta.Height);
                    if (minDist > dist) { minCapture = Captures.Top; minDelta = delta; minDist = dist; minIndex = ri; }
    
                    delta.Height = e.Location.Y - r.Bottom;
                    dist = Math.Abs(delta.Height);
                    if (minDist > dist) { minCapture = Captures.Bottom; minDelta = delta; minDist = dist; minIndex = ri; }
                }
    
                if (r.Top < e.Location.Y && e.Location.Y < r.Bottom)
                {
                    delta.Width = e.Location.X - r.Left;
                    dist = Math.Abs(delta.Width);
                    if (minDist > dist) { minCapture = Captures.Left; minDelta = delta; minDist = dist; minIndex = ri; }
    
                    delta.Width = e.Location.X - r.Right;
                    dist = Math.Abs(delta.Width);
                    if (minDist > dist) { minCapture = Captures.Right; minDelta = delta; minDist = dist; minIndex = ri; }
                }
            }
    
            if (minDist < CAPTURE_DIST)
            {
                capture = minCapture;
                captIndex = minIndex;
                captDelta = minDelta;
                captRect = _rectangles[minIndex];
            }
            else
            {
                startLocation = e.Location;
            }
        }
    
        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            if (MouseButtons != MouseButtons.Left) return;
    
            if (capture != Captures.None)
            {
                setSize(e.Location);
                Invalidate();
            }
            else if (DrawingRectangle || DrawingEllipse)
            {
                setRectInProgress(e.Location);
                Invalidate();
            }
        }
    
        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            if (capture != Captures.None)
            {
                setSize(e.Location);
                capture = Captures.None;
                Invalidate();
            }
            else if (DrawingRectangle)
            {
                setRectInProgress(e.Location);
                _rectangles.Add(_rectInProgress);
                Invalidate();
            }
            else if (DrawingEllipse)
            {
                setRectInProgress(e.Location);
                _ellipses.Add(_rectInProgress);
                Invalidate();
            }
        }
    
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
    
            if (_rectangles.Any())
            {
                e.Graphics.DrawRectangles(new Pen(Color.Blue, 3), _rectangles.ToArray());
            }
            if (_ellipses.Any())
            {
                foreach (var ell in _ellipses)
                {
                    e.Graphics.DrawEllipse(new Pen(Color.Blue, 3), ell);
                }
            }
            if (MouseButtons == MouseButtons.Left && capture == Captures.None)
            {
                if (DrawingEllipse)
                {
                    e.Graphics.DrawEllipse(new Pen(Color.Red, 3), _rectInProgress);
                }
                else if (DrawingRectangle)
                {
                    e.Graphics.DrawRectangle(new Pen(Color.Red, 3), _rectInProgress);
                }
            }
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            DrawingRectangle = !DrawingRectangle;
            DrawingEllipse = false;
        }
    
        private void button2_Click(object sender, EventArgs e)
        {
            DrawingEllipse = !DrawingEllipse;
            DrawingRectangle = false;
        }
    }