Reputation: 1537
So I want to put a button in a JPanel
, but I want to keep it invisible/hidden unless the mouse pointer hovers over it. At this point, the button should be made visible, react to clicks and so on. When the mouse leaves the area, it should be hidden again.
I tried adding a MouseListener
to my JButton
and use setVisible()
, but when I hide the button (setVisible(false)
), then the listener doesn't work anymore - the application behaves as if the button is not there at all.
What's the correct way to implement this behavior?
Edit: I am using an absolute layout (setLayout(null)
) and I am manually placing my component using setBounds(x, y, width, height)
.
Upvotes: 6
Views: 6921
Reputation: 5625
You should deal with the mouse events on JPanel
. Get the mouse position on the JPanel
and see if it's within the JButton
's bounds.
While most of the LayoutManager
s ignore invisible components, so you can't always get the bounds of button when it is hidden -- Thanks to MadProgrammer. You should add an additional component to keep the "place" -- e.g. use a JPanel:
JPanel btnContainer = new JPanel(new BorderLayout()); // use BorderLayout to maximize its component
btnContainer.add(button); // make button the only component of it
panel.add(btnContainer); // panel is the JPanel you want to put everything on
panel.addMouseMotionListener(new MouseAdapter() {
public void mouseMoved (MouseEvent me) {
if (btnContainer.getBounds().contains(me.getPoint())) { // the bounds of btnContainer is the same as button to panel
button.setVisible(true); // if mouse position on JPanel is within the bounds of btnContainer, then make the button visible
} else {
button.setVisible(false);
}
}
});
button.addMouseLisener(new MouseAdapter() {
public void mouseExited (MouseEvent me) { // after thinking about it, I think mouseExited() is still needed on button -- because
// if you move your mouse off the button very quickly and move it out of panel's bounds,
// before panel captures any mouse move event, button will stay visible
button.setVisible(false); // And now, it will hide itself.
}
});
And there is another way to "simulate" an invisible button. You can override the paint()
method of JButton
class, clear a blank rectangle if "invisible":
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Demo extends JFrame {
class MyButton extends JButton {
private boolean show;
public MyButton (String text) { // You can implement other constructors like this.
super(text);
}
@Override
public void paint (Graphics g) {
if (show) {
super.paint(g);
} else {
g.setBackground(panel.getBackground());
g.clearRect(0, 0, getWidth(), getHeight());
}
}
public void setShow (boolean show) { // make a different name from setVisible(), use this method to "fakely" hide the button.
this.show = show;
repaint();
}
}
private MyButton btn = new MyButton("Yet another button");
private JPanel panel = new JPanel(new BorderLayout());
public Test () {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(500, 500);
setLocationRelativeTo(null);
btn.setShow(false);
btn.setPreferredSize(new Dimension(100, 100));
btn.addMouseListener(new MouseAdapter() { // capture mouse enter and exit events of the button, simple!
public void mouseExited (MouseEvent me) {
btn.setShow(false);
}
public void mouseEntered (MouseEvent me) {
btn.setShow(true);
}
});
panel.add(btn, BorderLayout.NORTH);
add(panel);
setVisible(true);
}
}
Upvotes: 3
Reputation: 816
seems to work OK as a CardLayout (button with empty label)
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
class Testing
{
JButton btn = new JButton("Click Me");
JLabel lbl = new JLabel();
CardLayout cl = new CardLayout();
JPanel cardLayoutPanel = new JPanel(cl);
public void buildGUI()
{
lbl.setPreferredSize(btn.getPreferredSize());
lbl.setBorder(BorderFactory.createLineBorder(Color.black));//testing size, remove later
cardLayoutPanel.add(lbl,"lbl");
cardLayoutPanel.add(btn,"btn");
JPanel p = new JPanel(new GridBagLayout());
p.add(cardLayoutPanel,new GridBagConstraints());
JFrame f = new JFrame();
f.getContentPane().add(p);
f.setSize(400,300);
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
MouseListener listener = new MouseAdapter(){
public void mouseEntered(MouseEvent me){
if(me.getSource() == lbl) cl.show(cardLayoutPanel,"btn");
}
public void mouseExited(MouseEvent me){
if(me.getSource() == btn) cl.show(cardLayoutPanel,"lbl");
}
};
lbl.addMouseListener(listener);
btn.addMouseListener(listener);
btn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae){
System.out.println("me clicked");
}
});
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable(){
public void run(){
new Testing().buildGUI();
}
});
}
}
Upvotes: 4
Reputation: 205875
One approach is to give the button no text, no border, and an empty icon sized to match a real rollover icon.
Addendum: This updated example relies on @Andrew's succinct empty icon seen here.
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
/** @see https://stackoverflow.com/a/14410594/230513 */
public class RollButton {
private static final int N = 64;
private void display() {
JFrame f = new JFrame("RollButton");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel p = new JPanel(new GridLayout());
p.setBorder(BorderFactory.createEmptyBorder(N, N, N, N));
p.add(createButton(UIManager.getIcon("OptionPane.errorIcon")));
f.add(p);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private JButton createButton(Icon icon) {
JButton b = new JButton();
b.setBorderPainted(false);
b.setText("");
// https://stackoverflow.com/a/14410597/230513
b.setIcon(new ImageIcon(new BufferedImage(icon.getIconWidth(),
icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB)));
b.setRolloverEnabled(true);
b.setRolloverIcon(icon);
return b;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new RollButton().display();
}
});
}
}
Upvotes: 4
Reputation: 168845
Use icons to reveal (colored) or hide (transparent) the button respectively.
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class InvisiButton {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
int size = 30;
JPanel gui = new JPanel(new GridLayout(4,10,4,4));
for (int ii=0; ii<40; ii++) {
JButton b = new JButton();
b.setContentAreaFilled(false);
b.setIcon(new ImageIcon(
new BufferedImage(size,size,BufferedImage.TYPE_INT_RGB)));
b.setRolloverIcon(new ImageIcon(
new BufferedImage(size,size,BufferedImage.TYPE_INT_ARGB)));
b.setBorder(null);
gui.add(b);
}
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
Upvotes: 7
Reputation: 347334
MouseEvent
(or other input events) are only going to be triggered when the component is actually visible.
You're other problem is the layout manager is probably going to ignore it when it does it layout, making the button (possibly) 0x0 width and height...
You could add the button to a custom panel (using a BorderLayout
), override the panel's getPreferredSize
and return the button's preferred size. This will allow the layout manager to layout out the panel, but allow you to put the button onto.
This could also be used to trap the mouse events on behalf of the button.
nb After some thinking, the above won't work. Once the button becomes visible, a mouseExit
event would be triggered, which would trigger the panel to hide the button. Once the button becomes visible, it would also start consuming mouse events, meaning that it would become near impossible to tell when the mouse has moved beyond the scope of the pane. Sure you could use a bunch of if
statements and flags to determine what's going, but there are easy ways...
Updated
Another approach is to create you're self a custom button and by overriding the paint
method, you can trick the component in appearing transparent, but still be notified of mouse events and get the benefits of the layout managers.
public class TestButton02 {
public static void main(String[] args) {
new TestButton02();
}
public TestButton02() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
TestButton btn = new TestButton("Testing");
btn.setBoo(false);
add(btn);
}
}
public class TestButton extends JButton {
private boolean boo;
public TestButton(String text) {
super(text);
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
setBoo(true);
}
@Override
public void mouseExited(MouseEvent e) {
setBoo(false);
}
});
}
public void setBoo(boolean value) {
if (boo != value) {
boo = value;
repaint();
}
}
public boolean isBoo() {
return boo;
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(isBoo() ? 1f : 0f));
super.paint(g2d);
g2d.dispose();
}
}
}
This basically has a special flag that effects a AlphaComposite
to either paint the button transparent or opaque...
Upvotes: 2