Show / hide JPopupMenu from JButton; FocusListener not working?

I need a JButton with a dropdown menu style menu attached. So I took JPopupMenu and attached it to JButton as you can see in the code below. What he needs to do is:

  • shows popup when clicked
  • hide him if he clicked a second time
  • hide it if the item is selected in the popup menu
  • hide it if the user clicks elsewhere on the screen

These 4 things work, but because of the Boolean flag that I use if the user clicks elsewhere or selects an item, I have to double-click on the button before it appears again. So I tried to add a FocusListener (which is absolutely not responding) to fix this and set the false flag in these cases.

EDIT: Last attempt in reply message ...

Here are the listeners: (This is in a class extending JButton, so the second listener is on 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 struggled with this for too long. If someone can let me know what’s wrong with that, it would be great!

Thanks!

The 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); } } 
+7
java swing jbutton jpopupmenu
source share
7 answers

Here is another approach that is not too harmful for hacking, if not elegant, and which, as far as I could tell, works. First, at the very top, I added the second logical name showPopup .

FocusListener should be as follows:

  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 value of isShowingPopup does not change anywhere - if it receives focus, it assumes it is shown, and if it loses focus, it assumes that it is not.

Then the ActionListener button on the button is different:

  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 a new bit. This is the MouseListener button on the button:

  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 is called before the menu loses focus, so isShowingPopup reflects whether the popup was displayed before the button was clicked. Then, if the menu were there, we simply set showPopup to false , so the actionPerformed method actionPerformed not display the menu after it is called (after the mouse is released).

This was done as expected, in each case, except for one thing: if the menu was shown and the user clicked the button on the button, but released it outside it, actionPerformed never called. This meant that showPopup remained false and the menu was not shown the next time the button was pressed. To fix this, the mouseReleased method resets showPopup . As far as I know, the mouseReleased method mouseReleased called after actionPerformed .

I played with the resulting button a bit, doing everything I could think of a button, and worked as expected. However, I am not 100% sure that events will always occur in the same order.

Ultimately, I think it's at least worth a try.

+1
source share

Here is a variant of the “big hack” version of the amber check I just made. Without the flag isShowingPopup ...

It is not bulletproof, but it works well enough until someone comes with an incredibly slow click to close the popup (or a very quick second click to open 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 the comments, this is not the most elegant solution, but it is terribly simple and works in 98% of cases.

Discover offers!

+3
source share

You can use JPopupMenu.isVisible () instead of your boolean to check the current state of the popup menu.

+1
source share

Have you tried adding a ComponentListener to JPopupMenu so you know when it was shown and hidden (and update the isShowingPopup flag isShowingPopup )? I'm not sure that listening to focus changes is necessarily correct.

+1
source share

What you need is a 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 pasted this into my code and verified that it works.

+1
source share

Well, I can’t be sure without seeing all your code, but is it possible that the popup does not focus at all? I had problems not having things properly configured in Swing before, so this could be a culprit. Try calling setFocusable(true) on the menu, and then call requestFocus() when you create the menu.

0
source share

I tried Tikhon Jelvis’s answer (introducing a smart combination of focusListener and mouseListener). This does not work for me on Linux (Java7 / gtk).: - (

Reading http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#requestFocus%28%29 says "Please note that using this method is not recommended because its behavior depends on platforms. "

It may happen that the order of the listening calls is changed using Java7 or it is changed using GTK vs Windows. I would not recommend this solution if you want to be platform independent.

BTW: I created a new account in stackoverflow to give this hint. It seems I am not allowed to comment on his answer (due to reputation). But it looks like I have a button for editing it. This stackoverflow is a very funny thing .:-)

0
source share

All Articles