Event detection on opaque pixels in JButton
Using the code example found in my question above, I have created several buttons with irregular edges that interlock, and am using a null layout in order to position the buttons properly. The issue I am encountering is that, although the mouse clicks are not being detected on transparent pixels in the bufferedimage, the button is still taking the shape of a rectangle. This means that buttons that are added to the panel later block portions of buttons they are adjacent to.
My question is: is there a way to force the mouse event to propagate down the entire physical arrangement of JButtons until it comes to one with opaque pixels, or is another solution required? I’ve looked at solutions involving Shape, but they seem very expensive, which is why I’m wondering about another way.
I’m not too attached to using JButtons, if the solution requires me to leave them, but I would like to find an inexpensive solution, if one exists.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class JButtonExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyButton button1 = null, button2 = null;
try {
button1 = new MyButton(ImageIO.read(new URL("https://dl.dropbox.com/s/dxbao8q0xeuzhgz/button1.png")));
} catch (Exception ex) {
ex.printStackTrace();
}
button1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent me) {
super.mouseClicked(me);
MyButton mb = ((MyButton) me.getSource());
if (!isAlpha(mb.getIconImage(), me.getX(), me.getY()))
JOptionPane.showMessageDialog(frame, "You clicked button 1");
}
private boolean isAlpha(BufferedImage bufImg, int posX, int posY) {
int alpha = (bufImg.getRGB(posX, posY) >> 24) & 0xFF;
return alpha == 0 ? true : false;
}
});
button1.setBounds(10, 10, 72, 77);
try {
button2 = new MyButton(ImageIO.read(new URL("https://dl.dropbox.com/s/v16kyha0ojx1gza/button2.png")));
} catch (Exception ex) {
ex.printStackTrace();
}
button2.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent me) {
super.mouseClicked(me);
MyButton mb = ((MyButton) me.getSource());
if (!isAlpha(mb.getIconImage(), me.getX(), me.getY()))
JOptionPane.showMessageDialog(frame, "You clicked button 2");
}
private boolean isAlpha(BufferedImage bufImg, int posX, int posY) {
int alpha = (bufImg.getRGB(posX, posY) >> 24) & 0xFF;
return alpha == 0 ? true : false;
}
});
button2.setBounds(65, 0, 122, 69);
frame.getContentPane().setLayout(null);
frame.add(button1);
frame.add(button2);
frame.setSize(210, 130);
frame.setVisible(true);
}
});
}
}
class MyButton extends JButton {
BufferedImage icon;
MyButton(BufferedImage bi) {
this.icon = ((BufferedImage) bi);
setContentAreaFilled(false);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(icon.getWidth(), icon.getHeight());
}
public BufferedImage getIconImage() {
return icon;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(icon, 0, 0, null);
g.dispose();
}
}
Alright, I’ve worked out a solution that does what I want it to do with enough accuracy for my usage case. While some of the particulars might be a little hackish, I think I’ve got it doing what I want now. Code to create
Areais adapted from here.For simplicity and ease of use in other areas of my larger program, I preserved each button as an independent
JButton. When a mouse click is detected on any button, it calculates the position of the click as it appears on the parent panel, then passes that position as aPointto the parent. The parent then runs through the array of buttons until it finds a button that contains the point, and fires the appropriate method. If the click is not within the area contained by any button, there is no effect. If the original mouse click does not occur on the square area bounding aJButton, no processing occurs.Although the calculations required to produce an
Areaare relatively expensive compared to the rest of the program, I can deal with this by creating all theAreaobjects on startup, since it is a very rare case that the program will be started and every area will not be used.I would also greatly appreciate if anyone could point out anything in this code (or outside of it) that is an issue. I assume that how the
GeneralPathcreates the area is by creating a 1px square on top of every pixel that meets the criteria (in this case, non-transparent pixels), and then creating an area object from the area contained by the path. Please correct me if I am wrong.