How to prevent the TrayIcon popup to occupy the whole dispatcher thread

扶醉桌前 提交于 2021-02-19 08:12:30

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!