How to prevent triggering of other events when closing a JPopupMenu by clicking outside it?
Asked Answered
C

2

6

There are some properties of the right-click context menu I would like to replicate with a JPopupMenu:

  1. When menu is open and you click elsewhere, menu closes.
  2. When menu is open and you click elsewhere, nothing else happens.

I've got the first part down just fine. But when I click elsewhere, other events can occur. For instance, lets say I have button, A, which performs some action, B. Currently, if the JPopupMenu is open, and I click A, the JPopupMenu closes and B is performed. I would prefer that JPopupMenu close and B NOT be performed. Is this possible?

Thanks

Canaletto answered 3/6, 2011 at 17:14 Comment(7)
I dont see why you wouldn't want the button event to fire when the user clicked on it, but whenever the JPopupMenu event is fired, disable the button click event, then reenable the event when the menu closes.Salazar
@BCarpe, is this a simple case of "check-then-act"? set a flag when the JPopupMenu is opened, and then have checks in your actionPerformed method(s) that will only carry out their routine if the flag is not set.Leukocyte
It's just the convention. Try it out yourself. Right click on your browser and click anywhere afterwards. It always closes first, before letting you click anything else. As far as disabling/re-enabling, I've got a lot of components and will probably be adding more, so keeping track of that mechanism would add a bit more complexity than I can really deal with.Canaletto
@mre, I'd really rather not turn to putting a method of handling the situation into each component. And is there a signal that JPopupMenu puts out when closing? --EDIT-- PopupMenuListener does so.Canaletto
@Canaletto If i right click in my browser then click a different tab than the one i am in, it selects the new tab. (Chrome)Salazar
@Hunter: @BCarpe: I can confirm Firefox does the same thing. It also works if you click on a link.Pompom
@Hunter Hmm... That's not what I get when using Chrome (@Pompom or Firefox)... But I'm on Ubuntu and if that's what you get, it must not be completely the convention. Perhaps I'll just leave it as it is and make the user click on empty space if they want to get rid of the popup menu...Canaletto
C
4

Taking into account what was said in your question and comments, I would approach your problem in one of the following ways.

Technically you have two options here:

1.Hide the popup whenever user moves the mouse outside of the popup. This way you do not have the problem of user clicking since the popup will disappear itself.

2.Capture this particular mouse event globally and consume the event on left click if the popup is visible. I show this particular solution in the example below.

    import java.awt.AWTEvent;
    import java.awt.Toolkit;
    import java.awt.event.AWTEventListener;
    import java.awt.event.ActionEvent;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import javax.swing.AbstractAction;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JMenu;
    import javax.swing.JPanel;
    import javax.swing.JPopupMenu;
    import javax.swing.SwingUtilities;

    public class DisableClickWhenPopupVisibleTest
    {
        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {               
                    final JPopupMenu popup = new JPopupMenu();
                    popup.add(new JMenu("aAaa"));
                    JPanel contentPane = new JPanel();
                    contentPane.add(popup);
                    JButton b = new JButton();
                    b.setAction(new AbstractAction("Button")
                    {
                        private static final long serialVersionUID = 1L;
                        @Override
                        public void actionPerformed(ActionEvent e)
                        {
                            System.out.println("b actionPerformed");
                        }
                    });
                    contentPane.add(b);
                    contentPane.addMouseListener(new MouseAdapter() {
                        @Override
                        public void mousePressed(MouseEvent e)
                        {
                            showPopup(e);
                        }
                        @Override
                        public void mouseReleased(MouseEvent e)
                        {
                            showPopup(e);
                        }
                        private void showPopup(MouseEvent e)
                        {
                            if(e.isPopupTrigger())
                                popup.show(e.getComponent(), e.getX(), e.getY());
                        }
                    });
                    //use global mouse event capture to disable left click on anything when popup is visible
                    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
                        @Override
                        public void eventDispatched(AWTEvent event)
                        {
                            MouseEvent me = (MouseEvent)event;
                            if(me.getID() == MouseEvent.MOUSE_PRESSED)
                            {
                                System.out.println("eventDispatched popup.vis="+popup.isVisible());
                                if( me.getButton() == MouseEvent.BUTTON3)
                                {   
                                    System.out.println("BUTTON3");
                                }   
                                else if(me.getButton() == MouseEvent.BUTTON1)
                                {
                                    System.out.println("BUTTON1");
                                    if(popup.isVisible())
                                        me.consume();
                                }
                            }
                        }
                    }, AWTEvent.MOUSE_EVENT_MASK);                      
                    JFrame f = new JFrame();
                    f.setContentPane(contentPane);
                    f.setSize(400, 300);
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.setVisible(true);
                }
            });
        }
    }

You can test the example by right clicking slightly on the left of the button then the popup will show. Then if you click over the button its action will not be called. The action is called normally if the popup is hidden. This functionality is provided by the following line of code Toolkit.getDefaultToolkit().addAWTEventListener(...). You can comment out the line and observe that then the action will occur in any case as you experience it currently.

Calabrese answered 7/6, 2011 at 19:45 Comment(1)
Thanks! Yeah, the globals listener was just what I was looking for.Canaletto
U
11

This works, and is a lot simpler... although could be overridden by some look and feels.

UIManager.put("PopupMenu.consumeEventOnClose", Boolean.TRUE);

It is also worth noting this only consumes the MOUSE_PRESSED event, the subsequent MOUSE_CLICKED event is not consumed. You can emulate mouse clicked by setting flag in mousePressed() and testing it in mouseReleased(). If the initial mouse pressed is consumed then the flag won't be set in the mouseReleased()

private boolean pressed = false;

@Override
public void mousePressed(MouseEvent e) {
    pressed = true;
}

@Override
public void mouseReleased(MouseEvent e) {
    if (pressed) {
         // do click stuff
    }
    pressed = false;
}
Unconventional answered 28/9, 2011 at 14:34 Comment(2)
This was useful to me to disable this behavior :) It caused a problem with subsequent right-clicks when popup menu already open, showing the new menu but not selecting the row.Ouzel
I have exactly the opposite problem. This value was true when I checked it, so I set it to false (and double-checked that it stays false), but my events are still consumed.Jeffers
C
4

Taking into account what was said in your question and comments, I would approach your problem in one of the following ways.

Technically you have two options here:

1.Hide the popup whenever user moves the mouse outside of the popup. This way you do not have the problem of user clicking since the popup will disappear itself.

2.Capture this particular mouse event globally and consume the event on left click if the popup is visible. I show this particular solution in the example below.

    import java.awt.AWTEvent;
    import java.awt.Toolkit;
    import java.awt.event.AWTEventListener;
    import java.awt.event.ActionEvent;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import javax.swing.AbstractAction;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JMenu;
    import javax.swing.JPanel;
    import javax.swing.JPopupMenu;
    import javax.swing.SwingUtilities;

    public class DisableClickWhenPopupVisibleTest
    {
        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {               
                    final JPopupMenu popup = new JPopupMenu();
                    popup.add(new JMenu("aAaa"));
                    JPanel contentPane = new JPanel();
                    contentPane.add(popup);
                    JButton b = new JButton();
                    b.setAction(new AbstractAction("Button")
                    {
                        private static final long serialVersionUID = 1L;
                        @Override
                        public void actionPerformed(ActionEvent e)
                        {
                            System.out.println("b actionPerformed");
                        }
                    });
                    contentPane.add(b);
                    contentPane.addMouseListener(new MouseAdapter() {
                        @Override
                        public void mousePressed(MouseEvent e)
                        {
                            showPopup(e);
                        }
                        @Override
                        public void mouseReleased(MouseEvent e)
                        {
                            showPopup(e);
                        }
                        private void showPopup(MouseEvent e)
                        {
                            if(e.isPopupTrigger())
                                popup.show(e.getComponent(), e.getX(), e.getY());
                        }
                    });
                    //use global mouse event capture to disable left click on anything when popup is visible
                    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
                        @Override
                        public void eventDispatched(AWTEvent event)
                        {
                            MouseEvent me = (MouseEvent)event;
                            if(me.getID() == MouseEvent.MOUSE_PRESSED)
                            {
                                System.out.println("eventDispatched popup.vis="+popup.isVisible());
                                if( me.getButton() == MouseEvent.BUTTON3)
                                {   
                                    System.out.println("BUTTON3");
                                }   
                                else if(me.getButton() == MouseEvent.BUTTON1)
                                {
                                    System.out.println("BUTTON1");
                                    if(popup.isVisible())
                                        me.consume();
                                }
                            }
                        }
                    }, AWTEvent.MOUSE_EVENT_MASK);                      
                    JFrame f = new JFrame();
                    f.setContentPane(contentPane);
                    f.setSize(400, 300);
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.setVisible(true);
                }
            });
        }
    }

You can test the example by right clicking slightly on the left of the button then the popup will show. Then if you click over the button its action will not be called. The action is called normally if the popup is hidden. This functionality is provided by the following line of code Toolkit.getDefaultToolkit().addAWTEventListener(...). You can comment out the line and observe that then the action will occur in any case as you experience it currently.

Calabrese answered 7/6, 2011 at 19:45 Comment(1)
Thanks! Yeah, the globals listener was just what I was looking for.Canaletto

© 2022 - 2024 — McMap. All rights reserved.