Reputation: 1991
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
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
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
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
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