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

时间:2010-03-11 01:20:53

标签: java swing jbutton jpopupmenu

我需要一个带有附加下拉样式菜单的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);
    }

}

7 个答案:

答案 0 :(得分:3)

以下是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%的情况下都有效。

向建议开放!

答案 1 :(得分:1)

您可以使用JPopupMenu.isVisible()代替布尔变量来检查弹出菜单的当前状态。

答案 2 :(得分:1)

您是否尝试将ComponentListener添加到JPopupMenu,以便了解它何时被显示和隐藏(并相应地更新isShowingPopup标志)?我不确定倾听焦点变化必然是正确的方法。

答案 3 :(得分:1)

你需要的是一个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;                     
            }
        });

我已将其插入您的代码并验证其有效。

答案 4 :(得分:1)

这是另一种方法,即使不是优雅的,也不是太糟糕的,而且就我所知,它可以起作用。首先,在最顶部,我添加了第二个名为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仍为假,下次按下按钮时菜单未显示。要解决此问题,mouseReleased方法会重置showPopup。据我所知,mouseReleased方法在actionPerformed之后被调用。

我玩了一下结果按钮,做了我能想到的所有按钮,它按预期工作。但是,我并非100%确定事件总是以相同的顺序发生。

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

答案 5 :(得分:0)

好吧,如果没有看到你的所有代码,我无法确定,但弹出窗口是否真的无法实现焦点?我之前在Swing中没有正确关注事物的问题,所以它可能是罪魁祸首。尝试在菜单上调用setFocusable(true),然后在出现菜单时调用requestFocus()

答案 6 :(得分:0)

我尝试了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的改变而改变。如果你想独立于平台,我不会推荐这个解决方案。

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