我需要一个带有附加下拉式菜单的 JButton。因此,我采用了 JPopupMenu 并将其附加到 JButton,如下面的代码所示。它需要做的是这样的:

  • 单击时显示弹出窗口
  • 如果第二次点击则隐藏它
  • 如果在弹出窗口中选择了某个项目,则隐藏它
  • 如果用户单击屏幕中的其他位置,则将其隐藏

这 4 件事是有效的,但由于我使用的布尔标志,如果用户单击其他位置或选择一个项目,我必须在按钮上单击两次才能再次显示。这就是为什么我尝试添加 FocusListener(绝对没有响应)来修复该问题并在这些情况下将标志设置为 false。

编辑:最后一次尝试回答帖子...

以下是听众:(它位于扩展 JButton 的类中,因此第二个侦听器位于 JButton 上。)

// Show popup on left click.
menu.addFocusListener(new FocusListener() {
 @Override
 public void focusLost(FocusEvent e) {
  System.out.println("LOST FOCUS");
  isShowingPopup = false;
 }

 @Override
 public void focusGained(FocusEvent e) {
  System.out.println("GAINED FOCUS");
 }
});

addActionListener(new ActionListener() {
 @Override
 public void actionPerformed(ActionEvent e) {
  System.out.println("isShowingPopup: " + isShowingPopup);
  if (isShowingPopup) {
   isShowingPopup = false;
  } else {
   Component c = (Component) e.getSource();
   menu.show(c, -1, c.getHeight());
   isShowingPopup = true;
  }
 }
});

我已经为此奋斗了太久了。如果有人可以告诉我这有什么问题,那就太好了!

谢谢!

代码:

public class Button extends JButton {

    // Icon.
    private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");

    // Unit popup menu.
    private final JPopupMenu menu;

    // Is the popup showing or not?
    private boolean isShowingPopup = false;

    public Button(int height) {
        super(ARROW_SOUTH);
        menu = new JPopupMenu(); // menu is populated somewhere else

        // FocusListener on the JPopupMenu
        menu.addFocusListener(new FocusListener() {
            @Override
            public void focusLost(FocusEvent e) {
                System.out.println("LOST FOCUS");
                isShowingPopup = false;
            }

            @Override
            public void focusGained(FocusEvent e) {
                System.out.println("GAINED FOCUS");
            }
        });

        // ComponentListener on the JPopupMenu
        menu.addComponentListener(new ComponentListener() {
            @Override
            public void componentShown(ComponentEvent e) {
                System.out.println("SHOWN");
            }

            @Override
            public void componentResized(ComponentEvent e) {
                System.out.println("RESIZED");
            }

            @Override
            public void componentMoved(ComponentEvent e) {
                System.out.println("MOVED");
            }

            @Override
            public void componentHidden(ComponentEvent e) {
                System.out.println("HIDDEN");
            }
        });

        // ActionListener on the JButton
        addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("isShowingPopup: " + isShowingPopup);
                if (isShowingPopup) {
                    menu.requestFocus();
                    isShowingPopup = false;
                } else {
                    Component c = (Component) e.getSource();
                    menu.show(c, -1, c.getHeight());
                    isShowingPopup = true;
                }
            }
        });

        // Skip when navigating with TAB.
        setFocusable(true); // Was false first and should be false in the end.

        menu.setFocusable(true);
    }

}
有帮助吗?

解决方案

这是另一种方法,即使不优雅,也不算太糟糕,而且据我所知,它是有效的。首先,在最顶部,我添加了第二个布尔值,称为 showPopup.

FocusListener 必须如下:

    menu.addFocusListener(new FocusListener() {
        @Override
        public void focusLost(FocusEvent e) {
            System.out.println("LOST FOCUS");
            isShowingPopup = false;
        }

        @Override
        public void focusGained(FocusEvent e) {
            System.out.println("GAINED FOCUS");
            isShowingPopup = true;
        }
    });

isShowingPopup boolean 在其他任何地方都不会改变——如果它获得焦点,它假设它已显示,如果它失去焦点,它假设它没有显示。

接下来, ActionListener 按钮上的不同:

   addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("isShowingPopup: " + isShowingPopup);
            if (showPopup) {
                Component c = (Component) e.getSource();
                menu.show(c, -1, c.getHeight());
                menu.requestFocus();
            } else {
                showPopup = true;
            }
        }
    });

现在真正的新内容来了。它是 MouseListener 在按钮上:

    addMouseListener(new MouseAdapter() {
        @Override
        public void mousePressed(MouseEvent e) {
            System.out.println("ispopup?: " + isShowingPopup);
            if (isShowingPopup) {
                showPopup = false;
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            showPopup = true;
        }
    });

基本上, mousePressed 在菜单失去焦点之前被调用,所以 isShowingPopup 反映在按下按钮之前是否显示弹出窗口。然后,如果菜单在那里,我们只需设置 showPopupfalse, , 所以这样 actionPerformed 方法一旦被调用(松开鼠标后)就不会显示菜单。

除以下情况外,这在所有情况下都按预期运行:如果菜单正在显示并且用户在按钮上按下鼠标但在按钮之外松开鼠标, actionPerformed 从未被调用过。这意味着 showPopup 仍然为 false,并且下次按下按钮时不会显示菜单。为了解决这个问题, mouseReleased 方法重置 showPopup. 。这 mouseReleased 方法在之后被调用 actionPerformed, ,据我所知。

我对最终的按钮进行了一些尝试,对按钮做了我能想到的所有事情,并且它按预期工作。然而,我不能 100% 确定事件总是以相同的顺序发生。

最终,我认为这至少值得尝试。

其他提示

这是我刚刚提出的 Amber Shah“大黑客”建议的一个变体。没有 isShowingPopup 标志...

它不是防弹的,但它工作得很好,直到有人以极其缓慢的点击来关闭弹出窗口(或者非常快的第二次点击来重新打开它......)。

public class Button extends JButton {

 // Icon.
 private static final ImageIcon ARROW_SOUTH = new ImageIcon("ArrowSouth.png");

 // Popup menu.
 private final JPopupMenu menu;

 // Last time the popup closed.
 private long timeLastShown = 0;

 public Button(int height) {
  super(ARROW_SOUTH);
  menu = new JPopupMenu(); // Populated somewhere else.

  // Show and hide popup on left click.
  menu.addPopupMenuListener(new PopupMenuListener() {
   @Override
   public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
    timeLastShown = System.currentTimeMillis();
   }
   @Override public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {}
   @Override public void popupMenuCanceled(PopupMenuEvent arg0) {}
  });
  addActionListener(new ActionListener() {
   @Override
   public void actionPerformed(ActionEvent e) {
    if ((System.currentTimeMillis() - timeLastShown) > 300) {
     Component c = (Component) e.getSource();
     menu.show(c, -1, c.getHeight());
    }
   }
  });

  // Skip when navigating with TAB.
  setFocusable(false);
 }

}

正如我在评论中所说,这不是最优雅的解决方案,但它非常简单,并且适用于 98% 的情况。

接受建议!

你可以使用 JPopupMenu.isVisible() 而不是布尔变量来检查弹出菜单的当前状态。

您是否尝试过添加 ComponentListenerJPopupMenu, ,以便您知道它何时显示和隐藏(并更新您的 isShowingPopup 相应标记)?我不确定倾听焦点变化是否一定是正确的方法。

您需要的是一个 PopupMenuListener:

        menu.addPopupMenuListener(new PopupMenuListener() {

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {

            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
                System.out.println("MENU INVIS"); 
                isShowingPopup = false;     
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent arg0) {
                System.out.println("MENU CANCELLED"); 
                isShowingPopup = false;                     
            }
        });

我将其插入到您的代码中并验证它是否有效。

好吧,如果没有看到您的所有代码,我无法确定,但是弹出窗口是否有可能实际上根本没有获得焦点?我之前在 Swing 中遇到过无法正确聚焦的问题,所以它可能是罪魁祸首。尝试打电话 setFocusable(true) 在菜单上然后调用 requestFocus() 当你让菜单出现时。

我尝试了Tikhon Jelvis的答案(引入focusListener和mouseListener的智能组合)。它在 Linux (Java7/gtk) 上对我不起作用。:-(

阅读 http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 上面写着“请注意,不鼓励使用此方法,因为它的行为依赖于平台”。

侦听器调用的顺序可能随 Java7 的变化而变化,或者随 GTK 与 Windows 的变化而变化。如果您想独立于平台,我不会推荐此解决方案。

顺便提一句:我在 stackoverflow 上创建了一个新帐户来给出这个提示。看来我不被允许对他的回答发表评论(因为声誉)。但似乎我有一个按钮可以编辑它。这个 stackoverflow 是一件非常有趣的事情。:-)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top