Swing:鼠标hover时显示/隐藏按钮

所以我想在JPanel放一个按钮,但我想让它保持不可见/隐藏,除非鼠标指针hover在它上面。 此时,应使按钮可见,对点击作出反应等。 当鼠标离开该区域时,应该再次隐藏它。

我尝试将MouseListener添加到我的JButton并使用setVisible() ,但是当我隐藏按钮( setVisible(false) )时,监听器不再起作用 – 应用程序的行为就像按钮根本不存在一样。

实现此行为的正确方法是什么?

编辑:我使用绝对布局( setLayout(null) ),我手动使用setBounds(x, y, width, height)放置我的组件。

使用图标分别显示(着色)或隐藏(透明)按钮。

在此处输入图像描述

 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); } } 

一种方法是给按钮没有文字,没有边框,以及一个大小匹配真实翻转图标的空图标。

附录:这个更新的例子依赖于@ Andrew 在这里看到的简洁空白图标。

 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(); } }); } } 

似乎可以正常工作作为CardLayout(带有空标签的按钮)

 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(); } }); } } 

您应该在JPanel上处理鼠标事件。 获取JPanel上的鼠标位置,看看它是否在JButton的范围内。

虽然大多数LayoutManager忽略了不可见的组件,但是当它被隐藏时你不能总是得到按钮的界限 – 感谢MadProgrammer。 你应该添加一个额外的组件来保持“位置” – 例如使用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. } }); 

还有另一种“模拟”隐形按钮的方法。 您可以覆盖JButton类的paint()方法,如果“不可见”则清除空白矩形:

 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); } } 

MouseEvent (或其他输入事件)仅在组件实际可见时才会被触发。

你还有一个问题是布局管理器可能会在布局时忽略它,使按钮(可能)为0x0宽度和高度……

您可以将按钮添加到自定义面板(使用BorderLayout ),覆盖面板的getPreferredSize并返回按钮的首选大小。 这将允许布局管理器布局面板,但允许您按下按钮。

这也可以用于代表按钮捕获鼠标事件。

经过一番思考后,上述情况无效。 一旦按钮变得可见,将触发mouseExit事件,这将触发面板隐藏按钮。 一旦按钮变得可见,它也将开始消耗鼠标事件,这意味着当鼠标移动到窗格范围之外时几乎不可能知道。 当然你可以使用一堆if语句和标志来确定发生了什么,但有很简单的方法……

更新

另一种方法是创建自己的自定义按钮,并通过覆盖paint方法,您可以欺骗组件显示透明,但仍然会收到鼠标事件的通知并获得布局管理器的好处。

 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(); } } } 

这基本上有一个特殊的标志,可以使AlphaComposite绘制透明或不透明的按钮……