从JButton显示/隐藏JPopupMenu; FocusListener无法正常工作?

我需要一个带有附加下拉样式菜单的JButton。 所以我拿了一个JPopupMenu并以你在下面的代码中看到的方式将它附加到JButton。 它需要做的是:

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

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

编辑:最后一次尝试回答post……

以下是监听器:(它在一个扩展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布尔值在其他任何地方都不会改变 – 如果它获得焦点,它会假定它已经显示,如果它失去了焦点,它就会认为它没有。

接下来,按钮上的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反映了在按下按钮之前是否显示了弹出窗口。 然后,如果菜单在那里,我们只是将showPopup设置为false ,这样actionPerformed方法一旦被调用就不显示菜单(在放开鼠标之后)。

这种情况在每种情况下都表现如预期但只有一种:如果显示菜单并且用户将鼠标按在按钮上但是将其释放到其外部,则从未调用actionPerformed 。 这意味着showPopup仍为false,下次按下按钮时菜单未显示。 要解决此问题, mouseReleased方法会重置showPopup 。 据我所知,在actionPerformed之后调用mouseReleased方法。

我玩了一下结果按钮,做了我能想到的所有按钮,它按预期工作。 但是,我并非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()而不是布尔变量来检查弹出菜单的当前状态。

您是否尝试将ComponentListener添加到JPopupMenu ,以便您知道它何时被显示和隐藏(并相应地更新您的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; } }); 

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

好吧,如果没有看到你的所有代码,我无法确定,但弹出窗口是否真的无法实现焦点? 我之前遇到过没有在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是一个非常有趣的事情。 🙂