8

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

}
4

7 回答 7

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% 的情况。

接受建议!

于 2010-03-11T20:42:14.650 回答
1

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

于 2010-03-11T03:17:32.543 回答
1

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

于 2010-03-11T10:16:22.540 回答
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;                     
            }
        });

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

于 2010-03-11T17:42:53.717 回答
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反映了在按下按钮之前是否显示了弹出窗口。然后,如果菜单在那里,我们只需设置showPopupfalse,这样actionPerformed一旦调用该方法(松开鼠标后),该方法就不会显示菜单。

这在所有情况下都按预期运行,但有一种情况:如果菜单正在显示并且用户在按钮上按下鼠标但在按钮之外释放它,actionPerformed则永远不会调用。这意味着它showPopup仍然是错误的,并且下次按下按钮时菜单不会显示。要解决此问题,该mouseReleased方法将重置showPopup. 据我所知,该mouseReleased方法在 之后被调用。actionPerformed

我玩了一会儿生成的按钮,对按钮做了我能想到的所有事情,它按预期工作。但是,我不能 100% 确定事件将始终以相同的顺序发生。

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

于 2010-03-11T23:51:34.363 回答
0

好吧,如果没有看到您的所有代码,我无法确定,但是弹出窗口是否可能根本没有真正获得焦点?我以前在 Swing 中无法正确集中注意力的问题,所以这可能是罪魁祸首。尝试setFocusable(true)在菜单上调用,然后requestFocus()在出现菜单时调用。

于 2010-03-11T05:05:00.300 回答
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 发生了更改。如果您想独立于平台,我不会推荐此解决方案。

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

于 2013-01-24T08:26:03.297 回答