Close popup-menu when it's already shown

275 views Asked by At

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

There are 0 answers