简体   繁体   中英

How do I associate a mouse click with a drawn object in C#?

I have a picturebox with a bunch of rectangles drawn over it (highlighting some features of the image). I want to determine if my user clicked within a given rectangle, and add an action specific to that rectangle (ie show additional information). How do I do this?

I can provide more information if desired, I'm just not sure what information would be useful at this point.

Current code to draw rectangles. rectX, rectY, rectRot, rectColor are all currently arrays. rectW and rectH are constants.

private void pbPicture_Paint(object sender, PaintEventArgs e)
    {
      for(int i = 0; i < rectX.Length; i++)
      {
        e.Graphics.ResetTransform();
        e.Graphics.TranslateTransform(rectX[i], rectY[i]);
        e.Graphics.RotateTransform(rectRot[i]);
        e.Graphics.DrawRectangle(new Pen(rectColor[i], penWidth), 0, 0, rectW, rectH);
      }
      e.Graphics.ResetTransform();
    }

Edit: added link to picture, additional code.

It'll be easier if you apply and keep the transform data for each shape, then you can use that in your implementation to draw the shapes, interact with the mouse inputs...etc. without doing any additional transform calls to draw the main shapes, nor math routines to find out whether a shape/rectangle contains a given Point.

Consider the Shape class here which encapsulates the relevant data and functionalities that you'll need in your implementation. Using the GraphicsPath class to keep the shape and apply the transform, as well as to use the GraphicsPath.IsVisible method to determine whether the shape contains a given point so you can take an action accordingly. Keeping and exposing the Matrix instance is to use it to transform the graphics in case you need to do more drawings over the shape like drawing text, image...etc.

using System;
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Collections.Generic;

public class Shape : IDisposable
{
    private bool disposedValue;
    private Matrix mx;
    private GraphicsPath gp;
    private Size sz;
    private Point loc;
    private float rot;

    public string Text { get; set; }
    public Size Size
    {
        get => sz;
        set
        {
            if (sz != value)
            {
                sz = value;
                CleanUp();
            }
        }
    }
    public Point Location
    {
        get => loc;
        set
        {
            if (loc != value)
            {
                loc = value;
                CleanUp();
            }
        }
    }
    public float Rotation
    {
        get => rot;
        set
        {
            if (rot != value)
            {
                rot = value;
                CleanUp();
            }
        }
    }
    public Matrix Matrix
    {
        get
        {
            if (mx == null)
            {
                mx = new Matrix();
                // According to your code snippet, you don't need to offset here.
                // mx.Translate(Location.X, Location.Y);
                mx.RotateAt(Rotation, Center);
            }
            return mx;
        }
    }
    public GraphicsPath GraphicsPath
    {
        get
        {
            if (gp == null)
            {
                gp = new GraphicsPath();
                gp.AddRectangle(Rectangle);
                gp.Transform(Matrix);
            }
            return gp;
        }
    }
    public Point Center
    {
        get
        {
            var r = Rectangle;
            return new Point(r.X + r.Width / 2, r.Y + r.Height / 2);
        }
    }
    public Rectangle Rectangle => new Rectangle(Location, Size);
    public bool Selected { get; set; }
    public Color BorderColor { get; set; } = Color.Black;
    // Add more, ForeColor, BackColor ...etc.

    public bool Contains(Point point) => GraphicsPath.IsVisible(point);

    private void CleanUp()
    {
        gp?.Dispose();
        gp = null;
        mx?.Dispose();
        mx = null;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing) CleanUp();
            disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

Having that, your implementation should be as simple as:

private readonly List<Shape> shapes = new List<Shape>();
private const int recW = 100;
private const int recH = 20;

// A method to create the list...
private void SomeMethod()
{
    shapes.ForEach(s => s.Dispose());
    shapes.Clear();

    // In some loop...
    var shape = new Shape
    {
        Text = "Shape...",
        Size = new Size(recW, recH),
        Location = new Point(some.X, some.Y),
        Rotation = someAngle
    };
    shapes.Add(shape);
    // Add the reset...

    pbox.Invalidate();
}

// And to dispose of them...
protected override void OnFormClosed(FormClosedEventArgs e)
{
    base.OnFormClosed(e);
    shapes.ForEach(x => x.Dispose());
}

The drawing part:

private void pbox_Paint(object sender, PaintEventArgs e)
{
    var g = e.Graphics;

    using (var sf = StringFormat.GenericTypographic)
    {
        sf.Alignment = sf.LineAlignment = StringAlignment.Center;

        shapes.ForEach(s =>
        {
            using (var pnBorder = new Pen(s.BorderColor))
            {                
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.PixelOffsetMode = PixelOffsetMode.Half;
                if (s.Selected) g.FillPath(Brushes.DarkOrange, s.GraphicsPath);
                g.DrawPath(pnBorder, s.GraphicsPath);

                if (!string.IsNullOrEmpty(s.Text))
                {
                    g.SmoothingMode = SmoothingMode.None;
                    g.PixelOffsetMode = PixelOffsetMode.Default;
                    g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                    g.Transform = s.Matrix;
                    g.DrawString(s.Text, Font, Brushes.Black, s.Rectangle, sf);
                    g.ResetTransform();
                }
            }
        });
    }
}

Interacting with the mouse events:

private void pbox_MouseDown(object sender, MouseEventArgs e)
{
    foreach (var shape in shapes)
        shape.Selected = shape.Contains(e.Location);

    pbox.Invalidate();
}

I've created rectangles (Shape objects) with random values to demo.

SO73294478

Note: Some offset ( Matrix.Translate(...) ) also applied here to have some space between the shapes.

\You can use the event argument e to get the mouse co ordinates

private void pictureBox1_Click(object sender, EventArgs e)
{
    MouseEventArgs me = (MouseEventArgs)e;
    Point coordinates = me.Location;
}

There is one more link to help you identify the click events on the shape -

https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/ide/tutorial-3-create-a-matching-game?view=vs-2015&redirectedfrom=MSDN

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM