Danny King
Danny King

Reputation: 1991

JPopupMenu won't close if I click outside of it

I have created a Java Swing app that has no visible main window but which is controlled through its tray icon by right-clicking.

I am using a JPopupMenu for this, but when I click outside of the popup menu (e.g. on another application's window or the desktop) the JPopupMenu does not disappear which is not the expected behaviour.

Originally I was using a popup menu which did work as expected but this did not allow me to have icons in the menu.

How can I get it to close when I click elsewhere, as expected?

Upvotes: 9

Views: 5728

Answers (4)

Ben
Ben

Reputation: 21

I was recently struggeling with this "old" issue. Here comes my solution which works well & to my expectation.

I added an undecorated transparent window to the tray icon class and attached a FocusListener to it. When the popup is triggered to be shown, the mouse event also triggers the transparent window to be shown (which cannot be seen due to its transparency). The window gets a focus gained event. When the mouse is clicked somewhere on the screen outside the popup it triggers the focusLost event of the transparent window. This event is now used to set the popup to invisible.

public MyTrayIconApp() {
    if (!SystemTray.isSupported()) {
        System.out.println("SystemTray is not supported");
        System.exit(0);
    }

    // work around to close popup if mouse is clicked outside of popup
    transparentWindow = new JFrame();
    transparentWindow.setType(JFrame.Type.UTILITY);  // avoid task bar icon
    transparentWindow.setUndecorated(true);
    transparentWindow.setOpacity(0.0f);
    transparentWindow.addFocusListener(new FocusListener(){
        @Override
        public void focusGained(FocusEvent e) {}

        @Override
        public void focusLost(FocusEvent e) {
            if (popup.isVisible())
                popup.setVisible(false);
                transparentWindow.dispose();
        }           
    });        
    
    popup = new JPopupMenu();
    icon = new ImageIcon(IndexWatcherTrayIcon.class.getResource("/16x16/myIcon.png"));
    trayIcon = new TrayIcon(icon);
    tray = SystemTray.getSystemTray();

    // Create pop-up menu components
    JMenuItem startstopItem = new JMenuItem("Stop");
    JMenuItem updateItem = new JMenuItem("Update Indexes");
    JMenuItem exitItem = new JMenuItem("Exit");        
  
    // Add components to pop-up menu       
    popup.add(startstopItem);
    popup.add(updateItem);
    popup.addSeparator();
    popup.add(exitItem);
    
    // Add mouse listener to tray icon
    trayIcon.addMouseListener(new MouseAdapter() {                   
        @Override
        public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    transparentWindow.setVisible(true);
                    popup.setLocation(e.getX(), e.getY());
                    popup.setInvoker(popup);
                    popup.setVisible(true);                                  
                }         
            }
        });
    
    // Add tray icon to system tray              
    try {
        tray.add(trayIcon);
    } catch (AWTException e) {
        System.out.println("TrayIcon could not be added.");
    }         
}

Upvotes: 2

Kezine
Kezine

Reputation: 71

//_Popup is your JPopupMenu, call this method before setting your popup to visible
public void armPopup()
{
    if(_Popup != null)
    {
        Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener()
        {
            @Override
            public void eventDispatched(AWTEvent event) {

                if(event instanceof MouseEvent)
                {
                    MouseEvent m = (MouseEvent)event;
                    if(m.getID() == MouseEvent.MOUSE_CLICKED)
                    {
                        _Popup.setVisible(false);
                        Toolkit.getDefaultToolkit().removeAWTEventListener(this);
                    }
                }
                if(event instanceof WindowEvent)
                {
                    WindowEvent we = (WindowEvent)event;
                    if(we.getID() == WindowEvent.WINDOW_DEACTIVATED || we.getID() == WindowEvent.WINDOW_STATE_CHANGED)
                    {
                        _Popup.setVisible(false);
                        Toolkit.getDefaultToolkit().removeAWTEventListener(this);
                    }
                }
            }

        }, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.WINDOW_EVENT_MASK);

    }
}

Upvotes: 7

Danny King
Danny King

Reputation: 1991

In the end I "solved" this by hacking around the issue. As Camickr points out, JPopupMenu is pretty buggy. However it is the only Swing popup menu implementation that allows you to have an icon next to each menu element.

My solution was to implement a listener on the jpopupmenu that, if the user put the mouse over the menu, after 3 seconds it would be set to .isVisible(false) if the user did not put the mouse back over the menu within that time.

In order to achieve this, I had to use a separate Thread which constantly checked if the popupmenu was active. If so, check if the mouse was over it, using an event listener, and set visibility to false if the user didn't enter it again within 3 seconds.

This is not a perfect solution as the user still has to wait 3 seconds for the menu to disappear (it should be instant if he/she clicks away) and it will disappear even if it is in focus (it should not unless the user clicks away). However, it felt 'good enough' to be accepted.

Hope that helps.

Upvotes: 3

camickr
camickr

Reputation: 324207

There are know bugs when using JPopupMen: JTrayIcon: support Swing JPopupMenus for tray icons. Near the end is a link to a possible solution. I haven't tried it so I don't know if it will fix your problem or not.

Upvotes: 2

Related Questions