3

I have a picture in PNG with transparent and normal color.

I use this for one Button:

this.Button1.BackColor = System.Drawing.Color.Transparent;
this.Button1.BackgroundImage = Image;
this.Button1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
this.Button1.FlatAppearance.BorderSize = 0;
this.Button1.FlatAppearance.MouseDownBackColor = System.Drawing.Color.Transparent;
this.Button1.FlatAppearance.MouseOverBackColor = System.Drawing.Color.Transparent;
this.Button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.Button1.Location = new System.Drawing.Point(0, 0);
this.Button1.Margin = new System.Windows.Forms.Padding(0);
this.Button1.Name = "Skin";
this.Button1.Size = new System.Drawing.Size(294, 194);
this.Button1.TabIndex = 7;
this.Button1.UseVisualStyleBackColor = false;

And I when I click at transparent area, it still counts as a click on the Button.

How can I make this Button only call click event when I click at the colored area?

And when I click at the transparent area, I want it to count as a click on the object behind this button.

Kara
  • 6,115
  • 16
  • 50
  • 57
Tranoze
  • 27
  • 1
  • 5
  • You can restrict the Region of the Button if you can get at a GraphicsPath that excludes the transparent areas. Unfortunately you can't tranform a bitmap to a path.. – TaW May 31 '15 at 14:31
  • Yeah, i search for this for days but i see none solution for this. I really want to creat a custom button in winform but all i have to do is make that custom button become rectangle... – Tranoze May 31 '15 at 14:38
  • So, what do your images look like? Simple shapes can be done, but complicated images won't work with precision. See the start (only) of [this post](http://stackoverflow.com/questions/30134352/how-to-create-a-clickable-irregularly-shaped-region/30137794?s=1|0.0000#30137794) for an example to restrict the reion of a control to a shape. – TaW May 31 '15 at 14:49
  • It look like a leaf. – Tranoze May 31 '15 at 14:56
  • See my updated and simplyfied answer! – TaW May 31 '15 at 17:50

1 Answers1

2

You have two options:

  • Use a Button with a Region.

  • Use a Button with a BackgroundImage and check what the user hits with each Click

Option one is only feasible if you can create a Region, which takes a GraphicsPath, which means that you need to create the shape you need from the Graphics primitves like lines and curves etc..

If you only have a Bitmap with transparency, you are better off not using a Button with a Region.

Instead you can use your Button1 and on every click you check the transparency of the clicked pixel.

If it is transparent you call the click event of the control below it..

private void Button1_MouseClick(object sender, MouseEventArgs e)
{
    Size r = Button1.BackgroundImage.Size;
    // check that we have hit the image and hit a non-transparent pixel
    if ((e.X < r.Width && e.Y < r.Height) &&
            ((Bitmap)Button1.BackgroundImage).GetPixel(e.X, e.Y).A != 0)
    {
        Console.WriteLine("BUTTON clicked");  // test
        // do or call your click actions here!
    }
    // else pass the click on..
    else
    {
        // create a valid MouseEventArgs
        MouseEventArgs ee = new MouseEventArgs(e.Button, e.Clicks, 
                                e.X + Button1.Left, e.Y + Button1.Top, e.Delta);
        // pass it on to the stuff below us
        pictureBox1_MouseClick(pictureBox1, ee);

        Console.WriteLine("BUTTON NOT clicked");  // test
    }
}

Note that the check assumes that you have a normal layout, with the button image at the top left and no scaling. If you need to scale the image you should keep a scaled bitmap to do the checks on.. But if you can use an unscale image you should do so, as this will look better.

Note how I create a correct MouseEventArgs parameter for the control below, so you can access the button or the location of the mouse there as well..

Also note that it is easier to use the MouseClick event instead of the Click event as it has the mouse location already..

If you need/want to use the Click event instead, you can skip creating the EventArgs, as it doesn't have meaningful data; just pass out the e from the click..

Here is how the Click event could start:

private void Button1_Click(object sender, EventArgs e)
{
    // we need the location of the clicked pixel:
    Point clickLocation = Button1.PointToClient(Control.MousePosition);
    // the rest can proceed as above, just with simple EventArgs..

If you want to the check on all mouse clicking event and pass each of them down to the parent you will have to code them all.

First let's look at the order of events on MSDN

  1. MouseDown event.
  2. Click event.
  3. MouseClick
  4. MouseUp event.

So we need to start at the MouseDown. We can do the test in a helper function hitTest, so we can re-use it..:

Button clickedButton = null;
MouseEventArgs ee = null;

void hitTest(Button btn, MouseEventArgs e)
{
    Size r = btn.BackgroundImage.Size;
    // check that we have hit the image and hit a non-transparent pixel
    if ((e.X < r.Width && e.Y < r.Height) &&
            ((Bitmap)btn.BackgroundImage).GetPixel(e.X, e.Y).A != 0)
    {
        clickedButton = btn;
        ee = new MouseEventArgs(e.Button, e.Clicks, e.X + btn.Left, e.Y + btn.Top, e.Delta);
    }
    else clickedButton = null;
}

Now we code all four events. We need to call hitTest only once and we pass the simple, unmodified e parameter in the Click event:

private void Button1_MouseDown(object sender, MouseEventArgs e)
{
    hitTest(sender as Button, e);
    if (sender != clickedButton)
        yourParent_MouseDown((sender as Button).Parent, ee);
    else // do your stuff
}

private void Button1_Click(object sender, EventArgs e)
{
    if (sender != clickedButton)
        yourParent_Click((sender as Button).Parent, e);
    else // do your stuff
}

private void Button1_MouseClick(object sender, MouseEventArgs e)
{
    if (sender != clickedButton)
        yourParent_MouseClick((sender as Button).Parent, ee);
    else // do your stuff
}

private void Button1_MouseUp(object sender, MouseEventArgs e)
{
    if (sender != clickedButton)
        yourParent_MouseUp((sender as Button).Parent, ee);
    else // do your stuff
}

Of course you need to code all four events for yourParent also..

TaW
  • 53,122
  • 8
  • 69
  • 111
  • Thank you verymuch! It work, and i use event mouse down mouse up instead of click.. – Tranoze May 31 '15 at 21:50
  • But is there anyway to call the button's Parent's mouse click/ mouse up/ mouse down (all mouse event)?. I want all mouse event go to parent are instead of only mouse click. – Tranoze May 31 '15 at 22:55
  • No, i doesnt mean that, i already did it, what i mean is even it Parent's doesnt have these mouse event handle, how can i make it still receive a mouse action at the transparent area? – Tranoze Jun 01 '15 at 12:31
  • I don't understand. What is the Parent? You can only make a call to events that exist in your code.. – TaW Jun 01 '15 at 12:33
  • I dont mean to call a event. I mean can we call a click "action" at a location, and control will check if that location have click event or not. – Tranoze Jun 02 '15 at 04:55
  • I still don't understand. What would that mean __without__ the button?? One __can__ send a Click action, but why would we do that? The user __has__ already clicked..! The control can't check without an event.. – TaW Jun 02 '15 at 05:02
  • because if we creat a custom control in C#, we wont know does it parents have click event or not. I want to bring this custom button to a control so everyone can use this later. – Tranoze Jun 02 '15 at 05:10
  • OK, you could use a delegate for this then. Your Button class would publish a Delegate like this: `public MouseClickEventHandler ButtonMouseClick; public delegate void MouseClickEventHandler(object sender, MouseEventArgs e);` and call it where appropriate: `private void button4_MouseClick(object sender, MouseEventArgs e) { if (sender != clickedButton) tp_back_MouseClick((sender as Button).Parent, ee); else if (ButtonMouseClick != null) ButtonMouseClick(sender, ee); }`. The UC can then subscribe to it..: – TaW Jun 02 '15 at 07:25
  • .. with its own MouseClick event:`ButtonMouseClick += yourUO_MouseClick;`. The same goes for all four events. – TaW Jun 02 '15 at 07:26