【问题标题】:Swing: Show/hide button on mouse overSwing:鼠标悬停时显示/隐藏按钮
【发布时间】:2013-01-02 20:09:47
【问题描述】:

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

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

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

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

【问题讨论】:

  • 首先,质疑这种行为的必要性。其次,质疑绝对定位的必要性。你能详细说明一下吗?
  • @trashgod 当然。我在屏幕上画了一条长长的黑线。我希望当我将鼠标悬停在线条的某些点/区域上时(比如说每 50 个像素),那里会出现一个小按钮。单击它,将打开一个对话框,获取一些输入,然后当您提交输入时,在按钮所在的完全相同的点处,将在线绘制一些东西(比如说一个正方形)。绝对定位可以让我将按钮准确地放在我想要的位置。
  • 我可以看到在翻转时突出显示或放大刻度线,但该标记应该是可见的。我会看一个自定义布局管理器,它可以利用任何模型指定路点。

标签: java swing events button listener


【解决方案1】:

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

您的另一个问题是布局管理器在进行布局时可能会忽略它,使按钮(可能)宽度和高度为 0x0...

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

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

nb经过一番思考,以上行不通。一旦按钮变得可见,就会触发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 以将按钮绘制为透明或不透明...

【讨论】:

    【解决方案2】:

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

    虽然大多数LayoutManagers 忽略了不可见的组件,所以当按钮被隐藏时,你不能总是获得按钮的边界——感谢 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);
        }
    }
    

    【讨论】:

    • 如果按钮是不可见的,它的布局边界是不可靠的,因为大多数布局管理器会忽略不可见的组件
    • @MadProgrammer,然后添加一个JPanel并将其设置为BorderLayout并将按钮添加为唯一的组件,然后获取JPanel的边界。感谢您的提醒,我会更改答案。
    • 其实,现在想想,那可能行不通。当您使按钮可见时,将触发mouseExit 事件,这将导致您将按钮设置为不可见。 mouseMoved 事件也会在按钮可见时被使用
    • @MadProgrammer 你是对的,mouseExit 会发生,但我不认为它会导致按钮再次不可见,因为我只实现了 mouseMove 方法并且它停止接收鼠标移动事件一旦按钮变得可见 - 按钮将保持可见。但是有一件事——如果我在 JPanel 捕获任何鼠标移动事件之前将鼠标从按钮上快速移出 JPanel 的边界,它将保持可见。所以我可能需要在那个 JButton 上再次使用 mouseExited()。
    • MosueExit 让我在思考过程中漫无目的 ;)
    【解决方案3】:

    一种方法是给按钮不带文本、不带边框和空图标,其大小与真正的鼠标悬停图标相匹配。

    附录:这个更新的示例依赖于@Andrew 的简洁空图标 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();
                }
            });
        }
    }
    

    【讨论】:

    • 巧合的是,这适用于 null 布局,尽管不推荐。
    • 供参考,原来的EmptyIcon仍然是here
    【解决方案4】:

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

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

    【讨论】:

    • @trashgod 很整洁。我喜欢new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB) //blank image! 的单行代码:)
    【解决方案5】:

    似乎可以作为 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();
          }
        });
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-09
      • 1970-01-01
      • 2011-02-09
      • 1970-01-01
      • 2013-02-18
      • 2016-04-26
      相关资源
      最近更新 更多