我有一个Java应用程序,使用JFrame以及TrayIcon ,我添加了一个PopupMenu的TrayIcon 。
当我点击TrayIconpopup菜单出现,但只要PopupMenu可见,主框架就会冻结。
我的第一个想法是事件调度线程被某人占用。 所以我写了一个小的示例应用程序,它使用了一个swing工作器和一个进度条。
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); } } }
如何重现
如何将另一个应用程序的窗口句柄最小化到系统托盘?
系统托盘上下文菜单空白
如何更改由Shell_NotifyIcon设置的systray图标文本样式
如何检查NotifyIcon是否隐藏
当您启动示例应用程序时,您将看到一个带有进度条的框架。 SwingWorker模拟进程动作。
SwingWorker发布进度值并将其打印到System.out 。
当我点击托盘图标('黑色')时,进度条冻结,popup菜单出现。 我可以看到SwingWorker仍然在后台运行,因为它将进度值输出到命令行。
调查
所以我使用jvisualvm来看看应用程序,它告诉我,只要popup窗口是可见的,事件分派器线程就非常忙碌。
我可以弄清楚看sun.awt.windows.WTrayIconPeer.showPopupMenu(int,int)是由sun.awt.windows.WTrayIconPeer.showPopupMenu(int,int)方法可见的。
此方法使用EventQueue.invokelater提交可运行的事件到事件派发线程。 在这个可运行的方法中,调用sun.awt.windows.WPopupMenuPeer._show方法。 这种方法是native , 它似乎实现了一个繁忙的等待循环,以便事件调度线程完全被这个方法占用 。 因此,只要popup菜单是可见的,其他与UI相关的任务就不会被执行。
有没有人知道一个解决scheme,使TrayIconpopup窗口显示时保持事件调度线程响应?
正确的行为托盘图标点击?
如何确保我的启动程序在加载系统托盘后运行?
为什么系统托盘图标会清除先前显示的图标?
Windows任务栏(Windows 7?) – 如何在“控制面板”通知对话框中设置应用程序名称
如何从C#系统托盘应用程序作为线程运行C程序?
我现在使用jpopupmenu作为解决方法,但不能将jpopupmenu直接添加到TrayIcon 。
诀窍是写一个Mouselistner
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); } }
并将其连接到TrayIcon
TrayIcon trayIcon = ...; jpopupmenu popupMenu = ...; jpopupmenuMouseAdapter jpopupmenuMouseAdapter = new jpopupmenuMouseAdapter(popupMenu); trayIcon.addMouselistner(jpopupmenuMouseAdapter);