I have a JPopupMenu, that I want to show on a button click and hide when the user clicks on the button a second time (JMenu-like behavior). I have troubles implementing the second part because the second click closes the popup menu automatically. Here is my code:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
@SuppressWarnings("serial")
public class PopupAction extends AbstractAction {
private final JPopupMenu popup;
public PopupAction(String text, JPopupMenu popup) {
super(text);
this.popup = popup;
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() instanceof Component && !popup.isVisible()) {
Component c = (Component) e.getSource();
popup.show(c, 0, c.getHeight());
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(PopupAction::createTestUI);
}
private static void createTestUI() {
JFrame frm = new JFrame("Test popup");
JPopupMenu popup = new JPopupMenu();
popup.add("Item 1");
popup.add("Item 2");
popup.add("Item 3");
JPanel p = new JPanel();
p.add(new JButton(new PopupAction("Items", popup)));
frm.add(p, BorderLayout.NORTH);
frm.setSize(500, 300);
frm.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frm.setLocationRelativeTo(null);
frm.setVisible(true);
}
}
I've found a solution but it looks fragile for me due to using the method EventQueue.getCurrentEvent() (I use it to check from a PopupMenuListener whether the curent event is the mouse pressed event on my button). Have somebody a better idea?
Here is my "fragile" solution (probably somebody can use it to find a better idea).
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
@SuppressWarnings("serial")
public class PopupAction extends AbstractAction {
private final JPopupMenu popup;
private boolean ignoreNextAction;
private final PopupMenuListener pml = new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
// do nothing
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
onClose();
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
onClose();
}
private void onClose() {
AWTEvent e = EventQueue.getCurrentEvent();
if (e instanceof MouseEvent && ((MouseEvent) e).getComponent() instanceof JButton) {
MouseEvent me = (MouseEvent) e;
JButton b = (JButton) me.getComponent();
if (PopupAction.this.equals(b.getAction()) && me.getID() == MouseEvent.MOUSE_PRESSED) {
ignoreNextAction = true;
}
}
}
};
public PopupAction(String text, JPopupMenu popup) {
super(text);
this.popup = popup;
popup.addPopupMenuListener(pml);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() instanceof Component && !ignoreNextAction) {
Component c = (Component) e.getSource();
popup.show(c, 0, c.getHeight());
}
ignoreNextAction = false;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(PopupAction::createTestUI);
}
private static void createTestUI() {
JFrame frm = new JFrame("Test popup");
JPopupMenu popup = new JPopupMenu();
popup.add("Item 1");
popup.add("Item 2");
popup.add("Item 3");
JPanel p = new JPanel();
p.add(new JButton(new PopupAction("Items", popup)));
frm.add(p, BorderLayout.NORTH);
frm.setSize(500, 300);
frm.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frm.setLocationRelativeTo(null);
frm.setVisible(true);
}
}