How to create a clickable irregularly-shaped region in c#

心已入冬 提交于 2019-11-28 11:25:32

These are two approaches to this problem:

  • Work with Regions.

  • Work with transparent Images.

The first method involves creating controls, e.g PictureBoxes or Panels , which have the shape of the image and are only clickable inside that shape.

This is nice, provided you have access to the vector outline that makes up the shape.

Here is an example that restricts the visible&clickable Region of a Panel to an irregularly-shaped blob created from a list of trace points:

List<Point> points = new List<Point>();
points.Add(new Point(50,50));points.Add(new Point(60,65));points.Add(new Point(40,70));
points.Add(new Point(50,90));points.Add(new Point(30,95));points.Add(new Point(20,60));
points.Add(new Point(40,55));

using (GraphicsPath gp = new GraphicsPath())
{
    gp.AddClosedCurve(points.ToArray());
    panel1.Region = new Region(gp);
}

Unfortunately making a Region from the points contained in it will not work; imagine a Region as a list of vector shapes, these are made up of points, but only to create containing vectors, not pixels..

You could trace around the shapes but that is a lot of work, and imo not worth it.

So if you don't have vector shapes: go for the second method:

This will assume that you have images (probably PNGs), which are transparent at all spots where no clicks should be accepted.

The simplest and most efficient way will be to put them in a list together with the points where they shall be located; then, whenever they have changed, draw them all into one image, which you can assign to a PictureBox.Image.

Here is a Mouseclick event that will search for the topmost Image in a List of Images to find the one that was clicked. To combine them with their locations I use a Tuple list:

List<Tuple<Image, Point>> imgList = new List<Tuple<Image, Point>>();

We search through this list in each MouseClick:

private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
    int found = -1;
    // I search backward because I drew forward:
    for (int i = imageList1.Images.Count - 1; i >= 0; i--)
    {
        Bitmap bmp = (Bitmap) imgList[i].Item1;
        Point  pt = (Point) imgList[i].Item2;
        Point pc = new Point(e.X - pt.X, e.Y - pt.Y);
        Rectangle bmpRect = new Rectangle(pt.X, pt.Y, bmp.Width, bmp.Height);
        // I give a little slack (11) but you could also check for > 0!
        if (bmpRect.Contains(e.Location) && bmp.GetPixel(pc.X, pc.Y).A > 11)
           { found = i; break; }
    }

    // do what you want with the found image..
    // I show the image in a 2nd picBox and its name in the form text:
    if (found >= 0) { 
        pictureBox2.Image = imageList1.Images[found];
        Text = imageList1.Images.Keys[found];
    }

}

Here is how I combined the images into one. Note that for testing I had added them to an ImageList object. This has serious drawbacks as all images are scaled to a common size. You probably will want to create a proper list of your own!

Bitmap patchImages()
{
    Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
    imgList.Clear();
    Random R = new Random(1);

    using (Graphics G = Graphics.FromImage(bmp) )
    {
        foreach (Image img in imageList1.Images)
        {
            // for testing: put each at a random spot
            Point pt = new Point(R.Next(333), R.Next(222));
            G.DrawImage(img, pt);
            // also add to the searchable list:
            imgList.Add(new Tuple<Image, Point>(img, pt));
        }
    }
    return bmp;
}

I called it at startup :

private void Form1_Load(object sender, EventArgs e)
{
    pictureBox1.Image = patchImages();
}

Aside: This way of drawing all the images into one, is also the only one that lets you overlap the images freely. Winforms does not support real transparency with overlapping Controls.. And testing one Pixel (at most) for each of your shapes is also very fast.

Here is a Winforms example of handling an image mask. When the user clicks on the mask image it pops up a message box. This basic technique can obviously be modified to suit.

public partial class Form1 : Form {
    readonly Color mask = Color.Black;
    public Form1() {
        InitializeComponent();
    }

    private void pictureBox1_Click(object sender, EventArgs e) {
        var me = e as MouseEventArgs;
        using (var bmp = new Bitmap(pictureBox1.Image)) {
            if (me.X < pictureBox1.Image.Width && me.Y < pictureBox1.Image.Height) {
                var colorAtMouse = bmp.GetPixel(me.X, me.Y);
                if (colorAtMouse.ToArgb() == mask.ToArgb()) {
                    MessageBox.Show("Mask clicked!");
                }
            }
        }
    }
}

pictureBox1 has an Image loaded from a resource of a heart shape that I free-handed.

Have you tried an image map?

http://www.w3schools.com/TAGS/tag_map.asp

That should give you what you need to get started making a map to overlay on your image.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!