PictureBox - Handle Click Event on Non-Transparent Area of Image - c#

I have to make a windows form in C# where two PictureBox are overlapping. TopPictureBox is containing a transparent png picture. By default TopPictureBox can be clicked by clicking any visible or transparent area of the image in TopPictureBox. But I want to make that TopPictureBox only can be clicked by clicking visible area of image, not in transparent area. Also I want to make that cursor will only change on the visible area of the image, not in transparent area.
IS THERE ANY WAY TO DO THESE?
I am using this code to make TopPictureBox transparent.
TopPictureBox.BackColor = Color.Transparent;
Thank You for Help.

Checking if a position in PictureBox is Transparent or not depends on the Image and SizeMode property of PictureBox.
You can not simply use GetPixel of Bitmap because the image location and size is different based on SizeMode. You should first detect the size and location of Image based on SizeMode:
public bool HitTest(PictureBox control, int x, int y)
{
var result = false;
if (control.Image == null)
return result;
var method = typeof(PictureBox).GetMethod("ImageRectangleFromSizeMode",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var r = (Rectangle)method.Invoke(control, new object[] { control.SizeMode });
using (var bm = new Bitmap(r.Width, r.Height))
{
using (var g = Graphics.FromImage(bm))
g.DrawImage(control.Image, 0, 0, r.Width, r.Height);
if (r.Contains(x, y) && bm.GetPixel(x - r.X, y - r.Y).A != 0)
result = true;
}
return result;
}
Then you can simply use HitTest method to check if the mouse is over a non-transparent area of PictureBox:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (HitTest(pictureBox1,e.X, e.Y))
pictureBox1.Cursor = Cursors.Hand;
else
pictureBox1.Cursor = Cursors.Default;
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
if (HitTest(pictureBox1, e.X, e.Y))
MessageBox.Show("Clicked on Image");
}
Also setting BackColor to Color.Transparent only makes the PictureBox transparent relative to it's parent. For example if you have 2 PictureBox in a Form setting the transparent back color, just cause you see the background of form. To make a PictureBox which supports transparent background, you should draw what is behind the control yourself. You can find a TransparentPictureBox in this post: How to make two transparent layer with c#?

One way is to check whether the colour of the pixel where the user clicked, is the same as the background colour of the form. If yes, then the user clicked on a transparent area.
(Note : As Reza mentioned, this code can be used only when there are no overlapping PictureBoxes, i.e. only when the transparent area of the image is of the same colour as the Form's background)
Color pixelColour;
private void myPicturebox_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
pixelColour = ((Bitmap)myPicturebox.Image).GetPixel(point.X, point.Y);
if (this.BackColor == pixelColour)
{
// User clicked on transparent area
}
else
{
// User clicked on image
}
}
}

Related

How to repaint certain part of panel / or use two panels on top of each other with the top one having transparent background?

I have a C# WinForms project where I have to paint some things on my panel. I load in a grid from a file that translates to squares that are painted on the panel. Then I load in a file with dots that are then painted on top of the squares.
I then have a function that moves the dots around. But the repaint function is called every tick which causes the whole grid to flicker continously because it is painted so quick after each other.
How do I make it so that only the dots are repainted?
The repaint and paint functions are as follows:
private void Repaint(object sender, EventArgs e)
{
GridPanel.Invalidate();
}
private void GridPanel_Paint(object sender, PaintEventArgs e)
{
if ((GridPanel.Width != grid.Width || GridPanel.Height != grid.Height))
{
grid.Height = GridPanel.Height;
grid.Width = GridPanel.Width;
grid.setDimensions();
}
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.White), new Rectangle(0, 0, grid.Width, grid.Height));
foreach(Square square in grid.Squares)
{
if (square.Letter != '_')
{
SolidBrush color = new SolidBrush(square.Color);
g.FillRectangle(color, new Rectangle(square.X * grid.squareWidth, square.Y * grid.squareHeight, grid.squareWidth, grid.squareHeight));
}
}
foreach(Artist artist in grid.Artists)
{
SolidBrush color = new SolidBrush(artist.Color);
g.FillRectangle(color, new Rectangle(Convert.ToInt32(artist.X * grid.squareWidth), Convert.ToInt32(artist.Y * grid.squareHeight), grid.artistWidth, grid.artistHeight));
}
}
I also tried to use a second panel for the dots so I only have to repaint that one. But I cant get a transparent background working on the second panel, so the first panel is not visible this way.
Somebody knows a good solution for this problem?

PictureBox.Invalidate not re-rendering correctly

I'm trying to build a little test application (and my WinForm skills have rusted somewhat) with an Image and some overlays on top of it.
My image is set to stretch in the PictureBox but my fields on the right hand side I want to be from the origin of the image. Therefore I decided to render directly on the image that the PictureBox is using to ensure that the co-ordinates are always correct. Here's the white box rendering:
private void pbImage_Paint(object sender, PaintEventArgs e)
{
try
{
if (this.rdFront.Checked)
RenderFront(pbImage.Image, true);
else
RenderBack(pbImage.Image, true);
}
catch (ArgumentNullException ex)
{ }
}
public void RenderFront(Image image, bool includeBoxes)
{
// If we have no image then we can't render
if (image == null)
throw new ArgumentNullException("image");
Graphics gfx = Graphics.FromImage(image);
// Get the top label
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front))
{
if (includeBoxes)
{
// Fill a White rectangle and then surround with a black border
gfx.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
gfx.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
gfx.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}
The problem that I've got is if I do this and always draw on the underlying image then when I move the white overlay, I end up with un-drawn parts of the image. So I decided to clone the image before each re-render (on the basis that I don't care about performance).
I therefore decided to clone the image whenever I need to manually invalidate it, and call this when a setting changes:
public void Refresh()
{
if (this.rdFront.Checked)
pbImage.Image = new Bitmap(front);
else
pbImage.Image = new Bitmap(back);
this.pbImage.Invalidate();
}
Now I'm sure I must be missing something obvious - if I modify one of the values my penguins render with no overlay. However if I force a resize of the application then both the penguins and the overlay suddenly appear.
Can anyone suggest what I might be doing wrong?
Edit
Here's a download link to the project as it's quite small. Paste a path to an image in the 'Front Image' box and try using the controls on the right (set 100x100 height and width). Try re-sizing to see the desired affect. https://dl.dropboxusercontent.com/u/41796243/TemplateTester.zip
Controls and Forms have a Refresh method already. Are you really calling your Refresh method? Aren't you getting a warning that you should use the new keyword? Better give your Refresh method another name (e.g RefreshImage)!
I'm really not sure why you are using a picture box but then decide to do your on painting. I suggest to draw to an image off-screen and then simply assign it to the picture box:
public void RefreshImage()
{
Bitmap bmp;
if (this.rdFront.Checked)
bmp = new Bitmap(front);
else
bmp = new Bitmap(back);
using (Graphics gfx = Graphics.FromImage(bmp)) {
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front))
{
if (includeBoxes) {
// Fill a White rectangle and then surround with a black border
gfx.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
gfx.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
gfx.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}
pbImage.Image = bmp;
}
and remove the pbImage_Paint method.
Another possibility is to use the pbImage_Paint event handler in another way. Call the base.Paint() handler of the picture box that draws the image but leave the image itself unchanged. Instead draw on top of it by using the Graphics object given by the PaintEventArgs e argument. This Graphics object represents the client area of the picture box. This does not alter the Bitmap assigned to the picture box, but only draws on the screen.
private void pbImage_Paint(object sender, PaintEventArgs e)
{
base.Paint(); // Paints the image
if (this.rdFront.Checked)
RenderFront(e.Graphics, true);
else
RenderBack(e.Graphics, true);
}
public void RenderFront(Graphics g, bool includeBoxes)
{
foreach (MessageConfiguration config in this.config.Values.Where(c => c.Front)) {
if (includeBoxes) {
g.FillRectangle(Brushes.White, config.X, config.Y, config.Width, config.Height);
g.DrawRectangle(Pens.Black, config.X - 1, config.Y - 1, config.Width + 2, config.Height + 2);
}
g.DrawString(config.Text, new Font(FontFamily.GenericMonospace, config.FontSize), Brushes.Black, new PointF(config.X, config.Y));
}
}

I need to render an image on top of a drawn Object

I need to make a program that'll enable the user to customize his own car.
My problem is I have to draw the customizables by code and I have to add a PNG image of details on top of the drawn car.
The user has to select the colors, rim designs, and decals from the right
The car will be drawn when the PIMP button is pressed.
I have to add the PNG image, the second image, on top of the drawn image(first image), to make it look like the third image.
My current code looks like:
private void button1_Click(object sender, EventArgs e)
{
Graphics g;
g = this.CreateGraphics();
if (color == 1)
{
g.FillPolygon(blue, body);
}
else if (color ==2)
{
g.FIllPolygon(red, body);
}
g.FillPolygon(blackBrush, window);
pCard.Visible = True;
//pCard is an existing PictureBox where the Image is the cardetails.PNG
backcolor = transparent
}
When I press the PIMP button it draws the first image, but when it draws the PictureBox of cardetails.png, the transparent color displays the color gray and covers the first image.
I am very new to C# and Visual Basic. The only thing i know how to do here is to draw that blue car.
Load the image that contains the details you want to add:
Image decalImage = Image.FromFile("cardetails.png");
It would probably be best if you do not load it in your button1_Click method.
Then draw the image upon your graphics object using
g.DrawImage(decalImage, x, y);
Where x and y would be the position to draw it to.

Button image not aligned with text when the button is clicked?

I'm having a rather irritating problem with images on buttons in .NET. They don't behave as you would expect an image on a button to behave.
In the properties of a button you can set Image. So I select an image and the image shows up on the button! So far so good.
When a button is clicked, or in a pressed state, the text of the button will move down and right one pixel to create a depth. But not the image! It will stay in the same position, and it will look weird.
There is also the BackgroundImage property, but that's even worse! Because if I set BackgroundImageLayout to None instead of Center, the image will move up and left when pressed, the complete opposite direction of the text! What's up with that?
Anyway, what I want to achieve is a button image that moves just like the text would move when the button is in a pressed state. Is there a way to do this?
Just make a new image and paste the original one at an offset. Then set that as the Button's Image.
Example:
private void button1_MouseDown(object sender, MouseEventArgs e)
{
// replace "button_image.png" with the filename of the image you are using
Image normalImage = Image.FromFile("button_image.png");
Image mouseDownImage = new Bitmap(normalImage.Width + 1, normalImage.Height + 1);
Graphics g = Graphics.FromImage(mouseDownImage);
// this will draw the normal image at an offset on mouseDownImage
g.DrawImage(normalImage, 1, 1); // offset is one pixel each for x and y
// clean up
g.Dispose();
button1.Image = mouseDownImage;
}
private void button1_MouseUp(object sender, MouseEventArgs e)
{
// reset image to the normal one
button1.Image = Image.FromFile("button_image.png");
}
EDIT: The following function fixes a problem where the image would not 'pop' back up when the cursor leaves the button area while the mouse button is still pressed (see Labor's comment below):
private void button1_MouseMove(object sender, MouseEventArgs e)
{
Point relMousePos = e.Location;
bool mouseOverButton = true;
mouseOverButton &= relMousePos.X > 0;
mouseOverButton &= relMousePos.X < button1.Width;
mouseOverButton &= relMousePos.Y > 0;
mouseOverButton &= relMousePos.Y < button1.Height;
if (mouseOverButton != MouseButtons.None)
{
button1_MouseDown(sender, e);
}
else
{
button1_MouseUp(sender, e);
}
}

A PictureBox Problem

I have a problem:
I have 3 picture boxes with 3 different images as in Image
what can i set to pictureBox3 so both images look same.....
EDITED:
I want to move pictureBox3 on pictureBox2,
So there is no Option to merge them to single image
Make sure the image in pictureBox3 is transparent. Set the BackColor to transparent. In code, set the Parent property of the pictureBox3 to be pictureBox2. Adjust the Location coordinates of pictureBox3 since they will be relative to the coordinates of pictureBox2 once you've changed the Parent.
private void Form1_Load(object sender, EventArgs e)
{
pictureBox3.Parent = pictureBox2;
pictureBox3.Location =
new Point(
pictureBox3.Location.X
- pictureBox2.Location.X,
pictureBox3.Location.Y
- pictureBox2.Location.Y);
}
In designer you will not see the transparency, but at runtime you will.
Update
In the image, the left side shows the designer view, the right side is the runtime version.
Another update
I really don't understand how it would be possible that this doesn't work for you. I suppose there must be something we are doing different. I'll describe the exact steps to take to create a working sample. If you follow the exact same steps, I wonder if we'll get the same results or not. Next steps describe what to do and use two images I found on the net.
Using Visual Studio 2008, create a New Project using template Windows Forms Application. Make sure the project is targeted at the .NET Framework 3.5.
Set the Size of the Form to 457;483.
Drag a PictureBox control onto the form. Set its Location to 0;0 and its Size to 449;449.
Click the ellipsis besides its Image property, click the Import... button and import the image at http://a.dryicons.com/files/graphics_previews/retro_blue_background.jpg (just type the URL in the File name text box and click Open). Then click OK to use the image.
Drag another PictureBox onto the form, set its Location to 0;0 and its Size to 256;256. Also set its BackColor property to Transparent.
Using the same method as described above, import image http://www.axdn.com/redist/axiw_i.png which is a transparent image.
Now place the following code in the form's OnLoad event handler:
private void Form1_Load(object sender, EventArgs e)
{
pictureBox2.Parent = pictureBox1;
}
That's it! If I run this program I get a transparent image on top of another image.
I'll add another example that according to the updated requirement allows for moving image3.
To get it working, put an image with transparency in Resources\transp.png
This uses the same image for all three images, but you can simply replace transparentImg for image1 and image2 to suitable images.
Once the demo is started the middle image can be dragged-dropped around the form.
public partial class Form1 : Form
{
private readonly Image transparentImg; // The transparent image
private bool isMoving = false; // true while dragging the image
private Point movingPicturePosition = new Point(80, 20); // the position of the moving image
private Point offset; // mouse position inside the moving image while dragging
public Form1()
{
InitializeComponent();
//
// pictureBox1
//
this.pictureBox1.Location = new System.Drawing.Point(0, 0);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(231, 235);
this.pictureBox1.TabIndex = 0;
this.pictureBox1.TabStop = false;
this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox1_Paint);
this.pictureBox1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.pictureBox1_MouseDown);
this.pictureBox1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pictureBox1_MouseMove);
this.pictureBox1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.pictureBox1_MouseUp);
this.Controls.Add(this.pictureBox1);
transparentImg = Image.FromFile("..\\..\\Resources\\transp.png");
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.DrawImageUnscaled(transparentImg, new Point(20, 20)); // image1
g.DrawImageUnscaled(transparentImg, new Point(140, 20)); // image2
g.DrawImageUnscaled(transparentImg, movingPicturePosition); // image3
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
var r = new Rectangle(movingPicturePosition, transparentImg.Size);
if (r.Contains(e.Location))
{
isMoving = true;
offset = new Point(movingPicturePosition.X - e.X, movingPicturePosition.Y - e.Y);
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (isMoving)
{
movingPicturePosition = e.Location;
movingPicturePosition.Offset(offset);
pictureBox1.Invalidate();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
isMoving = false;
}
}
This code will do the trick:
using (Graphics g = Graphics.FromImage(pictureBox1.Image))
{
g.DrawImage(pictureBox2.Image,
(int)((pictureBox1.Image.Width - pictureBox2.Image.Width) / 2),
(int)((pictureBox1.Image.Height - pictureBox2.Image.Height) / 2));
g.Save();
pictureBox1.Refresh();
}
It will draw the image from pictureBox2 on the existing image of pictureBox1.
For starters, set the BackColor property of PictureBox3 to Transparent. This should work in almost all cases.
You should also use an image with a transparent background instead of white so you do not have the white borders around your purple circle. (Recommended image format: PNG)
Update
Following the replies I got, it appears setting the BackColor to Transparent doesn't work. In that case, it's best you handle the Paint event of the PictureBox and do the painting of the new image yourself as Albin suggested.
You might do some hack by overriding OnPaint and stuff, example here.
But I'd recommend to merge the pictures in pictureBox2 and 3 into a single image before displaying them in a single pictureBox.

Categories

Resources