简体   繁体   English

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

[英]Showing/hiding a JPopupMenu from a JButton; FocusListener not working?

I needed a JButton with an attached dropdown style menu. 我需要一个附带下拉式菜单的JButton。 So I took a JPopupMenu and attached it to the JButton in the way you can see in the code below. 所以我拿了一个JPopupMenu并以你在下面的代码中看到的方式将它附加到JButton。 What it needs to do is this: 它需要做的是:

  • show the popup when clicked 单击时显示弹出窗口
  • hide it if clicked a second time 如果再次点击则隐藏它
  • hide it if an item is selected in the popup 如果在弹出窗口中选择了某个项目,则隐藏它
  • hide it if the user clicks somewhere else in the screen 如果用户点击屏幕中的其他位置,则隐藏它

These 4 things work, but because of the boolean flag I'm using, if the user clicks somewhere else or selects an item, I have to click twice on the button before it shows up again. 这4件事情都有效,但由于我正在使用的布尔标志,如果用户点击其他地方或选择了一个项目,我必须在按钮上单击两次才会再次出现。 That's why I tried to add a FocusListener (which is absolutely not responding) to fix that and set the flag false in these cases. 这就是为什么我尝试添加一个FocusListener(绝对没有响应)来修复它并在这些情况下将标志设置为false。

EDIT: Last attempt in an answer post... 编辑:最后一次尝试回答帖子......

Here are the listeners: (It's in a class extending JButton, so the second listener is on the JButton.) 以下是监听器:(它在一个扩展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;
  }
 }
});

I've been fighting with this for way too long now. 我现在已经用这个太久了。 If someone can give me a clue about what's wrong with this, it would be great! 如果有人能给我一个关于这个问题的线索,那就太好了!

Thanks! 谢谢!

Code: 码:

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

}

Here's a variant of Amber Shah's "big hack" suggestion I just made. 这是我刚刚制作的Amber Shah的“大黑客”建议的变体。 Without the isShowingPopup flag... 没有isShowingPopup标志......

It's not bulletproof, but it works quite well until someone comes in with an incredibly slow click to close the popup (or a very fast second click to reopen it...). 它不是防弹的,但它可以很好地工作,直到有人用一个非常慢的点击来关闭弹出窗口(或者非常快速的第二次点击以重新打开它......)。

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

}

As I said in comments, that's not the most elegant solution, but it's horribly simple and it works in 98% of the cases. 正如我在评论中所说,这不是最优雅的解决方案,但它非常简单,并且在98%的情况下都有效。

Open to suggestions! 接受建议!

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

Have you tried adding a ComponentListener to the JPopupMenu , so that you know when it's been shown and hidden (and update your isShowingPopup flag accordingly)? 您是否尝试将ComponentListener添加到JPopupMenu ,以便您知道它何时被显示和隐藏(并相应地更新您的isShowingPopup标志)? I'm not sure listening for focus changes is necessarily the right approach. 我不确定倾听焦点变化必然是正确的方法。

What you need is a PopupMenuListener: 你需要的是一个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;                     
            }
        });

I inserted this into your code and verified that it works. 我将其插入到您的代码中并验证它是否有效。

Here is another approach which is not too bad of a hack, if not elegant, and which, as far as I could tell, works. 这是另一种方法,即使不是优雅的,也不是太糟糕的,而且就我所知,它是有效的。 First, at the very top, I added a second boolean called showPopup . 首先,在最顶层,我添加了第二个名为showPopup布尔值。

The FocusListener has to be as follows: 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;
        }
    });

The isShowingPopup boolean does not get changed anywhere else--if it gains focus, it assumes it's shown and if it loses focus, it assumes it isn't. isShowingPopup布尔值在其他任何地方都不会改变 - 如果它获得焦点,它会假定它已经显示,如果它失去了焦点,它就会认为它没有。

Next, the ActionListener on the button is different: 接下来,按钮上的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;
            }
        }
    });

Now comes the really new bit. 现在真的是新的一点。 It's a MouseListener on the button: 它是按钮上的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;
        }
    });

Basically, mousePressed gets called before the menu loses focus, so isShowingPopup reflects whether the popup was shown before the button is pressed. 基本上,在菜单失去焦点之前会调用mousePressed ,因此isShowingPopup反映了在按下按钮之前是否显示了弹出窗口。 Then, if the menu was there, we just set showPopup to false , so that the actionPerformed method does not show the menu once it gets called (after the mouse is let go). 然后,如果菜单在那里,我们只是将showPopup设置为false ,这样actionPerformed方法一旦被调用就不显示菜单(在放开鼠标之后)。

This behaved as expected in every case but one: if the menu was showing and the user pressed the mouse on the button but released it outside of it, actionPerformed was never called. 这种情况在每种情况下都表现如预期但只有一种:如果显示菜单并且用户将鼠标按在按钮上但是将其释放到其外部,则从未调用actionPerformed This meant that showPopup remained false and the menu was not shown the next time the button was pressed. 这意味着showPopup仍为false,下次按下按钮时菜单未显示。 To fix this, the mouseReleased method resets showPopup . 要解决此问题, mouseReleased方法会重置showPopup The mouseReleased method gets called after actionPerformed , as far as I can tell. 据我所知,在actionPerformed之后调用mouseReleased方法。

I played around with the resulting button for a bit, doing all the things I could think of to the button, and it worked as expected. 我玩了一下结果按钮,做了我能想到的所有按钮,它按预期工作。 However, I am not 100% sure that the events will always happen in the same order. 但是,我并非100%确定事件将始终以相同的顺序发生。

Ultimately, I think this is, at least, worth trying. 最终,我认为这至少值得尝试。

I tried the Answer of Tikhon Jelvis (introducing a smart combination of focusListener and mouseListener). 我尝试了Tikhon Jelvis的答案(引入了focusListener和mouseListener的智能组合)。 It does not work for me on Linux (Java7/gtk). 它在Linux(Java7 / gtk)上对我不起作用。 :-( :-(

Reading http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 there is written "Note that the use of this method is discouraged because its behavior is platform dependent." 阅读http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29写的“请注意,不鼓励使用此方法,因为它的行为是平台依赖“。

It may be that the order of listener calls changed with Java7 or it changes with GTK vs Windows. 可能是侦听器调用的顺序随Java7而改变,或者它随GTK与Windows的变化而变化。 I would not recommend this solution if you want to be platform independent. 如果您想要与平台无关,我不建议使用此解决方案。

BTW: I created a new account on stackoverflow to give this hint. 顺便说一句:我在stackoverflow上创建了一个新帐户来提供这个提示。 It seems I am not allowed to comment to his answer (because of reputation). 我似乎不允许对他的回答发表评论(因为声誉)。 But it seems I have a button to edit it. 但似乎我有一个按钮来编辑它。 This stackoverflow is a very funny thing. 这个stackoverflow是一个非常有趣的事情。 :-) :-)

Well, I can't be sure without seeing all of your code, but is it possible that the popup never actually gets focus at all? 好吧,如果没有看到你的所有代码,我无法确定,但弹出窗口是否真的无法实现焦点? I've had problems with things' not getting focus properly in Swing before, so it could be the culprit. 我之前在Swing中没有正确关注事物的问题,所以它可能是罪魁祸首。 Try calling setFocusable(true) on the menu and then calling requestFocus() when you make the menu appear. 尝试在菜单上调用setFocusable(true) ,然后在出现菜单时调用requestFocus()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM