考虑这个基本的Swing程序,由两个按钮组成:
public class main {
public static void main(String[] args) {
JFrame jf = new JFrame("hi!");
JPanel mainPanel = new JPanel(new GridLayout());
JButton longAction = new JButton("long action");
longAction.addActionListener(event -> doLongAction());
JButton testSystemOut = new JButton("test System.out");
testSystemOut.addActionListener(event -> System.out.println("this is a test"));
mainPanel.add(longAction);
mainPanel.add(testSystemOut);
jf.add(mainPanel);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void doLongAction() {
SwingUtilities.invokeLater(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("Interrupted!");
}
System.out.println("Finished long action");
});
}
}
我希望我的第二个按钮testSystemOut
可用,而第一个按钮正在进行长动作(这里,我在其中放了3秒钟)。我可以通过手动将doLongAction()
放入Thread
并致电start()
来实现。但我知道我应该使用SwingUtilities
代替EventQueue
,这与{{1}}完全相同。但是,如果我这样做,我的按钮会在其动作期间冻结。
为什么?
答案 0 :(得分:2)
使用SwingUtilities.invokeLater
,您在Swing事件线程上调用附带的代码,包括Thread.sleep(...)
调用,这是您应该从不做的事情,因为它放置整个事件线程,负责绘制GUI并响应用户输入的线程,即睡眠 - 即它冻结您的应用程序。解决方案:使用Swing Timer代替,或者在后台线程中休眠。如果要调用长时间运行的代码并使用Thread.sleep(...)
进行模拟,请使用SwingWorker为您完成后台工作。有关详细信息,请阅读Concurrency in Swing。请注意,没有理由让SwingUtilities.invokeLater
拥有它,因为ActionListener代码将在EDT(Swing事件线程)上调用,无论如何。但是,我会使用SwingUtilities.invokeLater
来创建GUI。
如,
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame jf = new JFrame("hi!");
JPanel mainPanel = new JPanel(new GridLayout());
JButton testSystemOut = new JButton("test System.out");
testSystemOut.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("this is a test");
}
});
mainPanel.add(new JButton(new LongAction("Long Action")));
mainPanel.add(new JButton(new TimerAction("Timer Action")));
mainPanel.add(testSystemOut);
jf.add(mainPanel);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
});
}
@SuppressWarnings("serial")
public static class LongAction extends AbstractAction {
private LongWorker longWorker = null;
public LongAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
@Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
longWorker = new LongWorker(); // create a new SwingWorker
// add listener to respond to completion of the worker's work
longWorker.addPropertyChangeListener(new LongWorkerListener(this));
// run the worker
longWorker.execute();
}
}
public static class LongWorker extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 3 * 1000;
@Override
protected Void doInBackground() throws Exception {
Thread.sleep(SLEEP_TIME);
System.out.println("Finished with long action!");
return null;
}
}
public static class LongWorkerListener implements PropertyChangeListener {
private LongAction longAction;
public LongWorkerListener(LongAction longAction) {
this.longAction = longAction;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
// if the worker is done, re-enable the Action and thus the JButton
longAction.setEnabled(true);
LongWorker worker = (LongWorker) evt.getSource();
try {
// call get to trap any exceptions that might have happened during worker's run
worker.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
@SuppressWarnings("serial")
public static class TimerAction extends AbstractAction {
private static final int TIMER_DELAY = 3 * 1000;
public TimerAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
@Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
new Timer(TIMER_DELAY, new TimerListener(this)).start();
}
}
public static class TimerListener implements ActionListener {
private TimerAction timerAction;
public TimerListener(TimerAction timerAction) {
this.timerAction = timerAction;
}
@Override
public void actionPerformed(ActionEvent e) {
timerAction.setEnabled(true);
System.out.println("Finished Timer Action!");
((Timer) e.getSource()).stop();
}
}
}
答案 1 :(得分:2)
如果要执行某些长时间运行的代码,请不要使用SwingUtilities.invokeLater(...)
。在单独的正常线程中执行此操作。
Swing不是多线程的,它是事件驱动的。因此,有SwingUtilities.invokeLater(...)
等方法。如果要从其他线程更改Swing-Components,则必须使用这些方法(因为Swing不是线程安全的),例如,如果要更改Button的文本。
GUI-Related的所有内容都在Swing-Thread中运行,例如:光标闪烁,来自操作系统的消息,用户命令等。
由于它是一个单独的线程,因此该线程中的每个长时间运行的代码都会阻止您的GUI。
如果您只是执行一些与GUI无关的长时运行的代码,它不应该在Swing-Event-Thread中运行,而应该在它自己的独立线程中运行。
请参阅 https://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html 为什么Swing不是多线程的。