async
async

Reputation: 1537

Swing: Show/hide button on mouse over

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

Answers (5)

shuangwhywhy
shuangwhywhy

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 LayoutManagers 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

Michael Dunn
Michael Dunn

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

trashgod
trashgod

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

Andrew Thompson
Andrew Thompson

Reputation: 168845

Use icons to reveal (colored) or hide (transparent) the button respectively.

enter image description here

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

MadProgrammer
MadProgrammer

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

Related Questions