问题
I have a java application that uses a JFrame
as well as a TrayIcon
and I added a PopupMenu
to the TrayIcon
.
When I click on the TrayIcon
the popup menu shows up, but the main frame freezes as long as the PopupMenu
is visible.
My first thought was that the event dispatch thread is occupied by someone. So I wrote a small example application that uses a swing worker and a progress bar.
public class TrayIconTest {
public static void main(String[] args) throws AWTException {
JFrame frame = new JFrame("TrayIconTest");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
JProgressBar jProgressBar = new JProgressBar();
BoundedRangeModel boundedRangeModel = jProgressBar.getModel();
contentPane.add(jProgressBar);
boundedRangeModel.setMinimum(0);
boundedRangeModel.setMaximum(100);
PopupMenu popup = new PopupMenu();
TrayIcon trayIcon =
new TrayIcon(new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB), "TEST");
// Create a popup menu components
MenuItem aboutItem = new MenuItem("About");
popup.add(aboutItem);
trayIcon.setPopupMenu(popup);
SystemTray tray = SystemTray.getSystemTray();
tray.add(trayIcon);
IndeterminateSwingWorker indeterminateSwingWorker = new IndeterminateSwingWorker(boundedRangeModel);
indeterminateSwingWorker.execute();
frame.setSize(640, 80);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class IndeterminateSwingWorker extends SwingWorker<Void, Integer> {
private BoundedRangeModel boundedRangeModel;
public IndeterminateSwingWorker(BoundedRangeModel boundedRangeModel) {
this.boundedRangeModel = boundedRangeModel;
}
@Override
protected Void doInBackground() throws Exception {
int i = 0;
long start = System.currentTimeMillis();
long runtime = TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES);
while (true) {
i++;
i %= boundedRangeModel.getMaximum();
publish(i);
Thread.sleep(20);
long now = System.currentTimeMillis();
if ((now - start) > runtime) {
break;
}
System.out.println("i = " + i);
}
return null;
}
@Override
protected void process(List<Integer> chunks) {
for (Integer chunk : chunks) {
boundedRangeModel.setValue(chunk);
}
}
}
How to reproduce
When you start the example application you will see a frame with a progress bar. A SwingWorker
simulates a progress action.
The SwingWorker
publishes the progress value and also prints it to System.out
.
When I click on the tray icon ('the black one') the progress bar freezes and the popup menu shows up. I can see that the SwingWorker is still running in the background, because it outputs the progress value to the command line.
Investigation
So I took a look at the application using jvisualvm
and it shows me that the event dispatcher thread is very busy as long as the popup is visible.
I could figure out that look at the popup is made visible by the method sun.awt.windows.WTrayIconPeer.showPopupMenu(int, int)
.
This method submits a runnable using EventQueue.invokeLater
to the event dispatch thread. In this runnable the method sun.awt.windows.WPopupMenuPeer._show
is invoked. This method is native
and it seems that it implements a busy wait loop so that the event dispatch thread is completely occupied by this method.
Thus other ui related tasks are not executed as long as the popup menu is visible.
Does anyone know a workaround that keeps the event dispatch thread responsive while a TrayIcon
popup is shown?
回答1:
I'm now using a JPopupMenu
as workaround, but you can't add the JPopupMenu
directly to the TrayIcon
.
The trick is to write a MouseListener
public class JPopupMenuMouseAdapter extends MouseAdapter {
private JPopupMenu popupMenu;
public JPopupMenuMouseAdapter(JPopupMenu popupMenu) {
this.popupMenu = popupMenu;
}
@Override
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
@Override
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
Dimension size = popupMenu.getPreferredSize();
popupMenu.setLocation(e.getX() - size.width, e.getY() - size.height);
popupMenu.setInvoker(popupMenu);
popupMenu.setVisible(true);
}
}
and connect it to the TrayIcon
TrayIcon trayIcon = ...;
JPopupMenu popupMenu = ...;
JPopupMenuMouseAdapter jPopupMenuMouseAdapter = new JPopupMenuMouseAdapter(popupMenu);
trayIcon.addMouseListener(jPopupMenuMouseAdapter);
来源:https://stackoverflow.com/questions/38200403/how-to-prevent-the-trayicon-popup-to-occupy-the-whole-dispatcher-thread