我面临的问题是我想向 JFrame 添加几个大组件(每个大组件 = 15k JPanel,每一个 10 个 JLabel)。添加每个大组件后,我想更新一个标签,显示已经添加了多少个大组件的计数器。
这是一个小例子,可以说明问题:
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// preparation
final JFrame frame = new JFrame();
final Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout(FlowLayout.LEFT));
final JLabel loadingLabel = new JLabel("Loading 0/5");
contentPane.add(loadingLabel);
// start GUI
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// adding big components and show progress
for (int i = 1; i <= 5; i++) {
final long time = System.currentTimeMillis();
contentPane.add(getBigJPanel());
System.out.println("time <i=" + i + ">: " + (System.currentTimeMillis() - time));
loadingLabel.setText("Loading " + i + "/5");
contentPane.revalidate();
contentPane.repaint();
}
});
}
private static JPanel getBigJPanel() {
final JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(300, 300));
for (int i = 0; i < 25_000; i++) {
panel.add(new JPanel());
}
return panel;
}
}
我有标签
loadingLabel
,它应该显示已添加的大组件的数量。它在开始时添加到 JFrame 中,之后 UI 应该启动并可见。然而,因为在 frame.setVisible(true)
之后代码开始添加大组件,所以标签没有被绘制。我假设 Swing 会尝试优化代码,并且在之后直接添加新组件时不会浪费 repaint
。
此外,添加每个大组件后,我更新标签的文本并调用
revalidate();repaint();
这应该更新 GUI。然而,没有任何进展。仅在添加每个大组件后,才会显示最后一个 loadingLabel
(应为“正在加载 5/5”)。
我想要的是 GUI 显示每个“正在加载 i/5”。
P.S.:我添加了一个时间测量来显示创建一个大组件并将其添加到内容窗格所需的时间。在我的 PC 上,每次大约产生 3000 毫秒。这个问题只有在产生高时间时才有意义。这意味着要在功能强大的 PC 上尝试该问题,您可能需要增加
25_000
的 getBigJPanel()
。
我尝试过的一件事是通过用
SwingUitilities.invokeLater(Runnable)
包装代码块来稍微重新安排 EDT 的操作。一方面,到目前为止它似乎工作得很好,然而,另一方面,它留下了一团糟,因为它需要在新的SwingUitilities.invokeLater(Runnable)
中递归地调用下一次迭代。
代替 for 循环:
SwingUtilities.invokeLater(() -> {
loadingLabel.setText("Loading " + 1 + "/5");
contentPane.revalidate();
contentPane.repaint();
SwingUtilities.invokeLater(() -> {
contentPane.add(getBigJPanel());
loadingLabel.setText("Loading " + 2 + "/5");
contentPane.repaint();
contentPane.revalidate();
contentPane.repaint();
SwingUtilities.invokeLater(() -> {
contentPane.add(getBigJPanel());
loadingLabel.setText("Loading " + 3 + "/5");
etc.
});
});
});
我也看过
SwingWorker
,但我认为这个课程是为除了在 EDT 上做一些事情之外的耗时任务而设计的。
当您调用
SwingUtilities.invokeLater(() ->
时,将运行负责更新 UI 的 EDT(事件调度线程)内的代码。
因此,当它继续运行时(您的代码在 EDT 内运行),不会更新任何 GUI。
因此,您可以在普通线程(EDT 除外)中构造 GUI 元素,只要它们不显示即可。但请记住只能从同一个线程访问这些元素!
一旦你完成并想要显示它,就让 EDT 拥有它
=> 所以,利用它,在外面进行施工,并且只将“准备好”的东西放入 EDT 中:
package stackoverflow.gui.edt.bigpanels;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(final String[] args) {
// preparation
final JFrame frame = new JFrame();
final Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout(FlowLayout.LEFT));
final JLabel loadingLabel = new JLabel("Loading 0/5");
contentPane.add(loadingLabel);
// start GUI
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// adding big components and show progress
for (int i = 1; i <= 5; i++) {
final int showIndex = i;
final long time = System.currentTimeMillis();
final JPanel newBigLabel = getBigJPanel();
System.out.println("time <i=" + showIndex + ">: " + (System.currentTimeMillis() - time));
SwingUtilities.invokeLater(() -> {
contentPane.add(newBigLabel);
loadingLabel.setText("Loading " + showIndex + "/5");
contentPane.revalidate();
contentPane.repaint();
});
}
}
private static JPanel getBigJPanel() {
final JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(300, 300));
for (int i = 0; i < 25_000; i++) {
panel.add(new JPanel());
}
return panel;
}
}