我的完整GUI在AWT线程内运行,因为我使用SwingUtilities.invokeAndWait(...)
启动主窗口。
现在我有一个JDialog,它只是显示一个JLabel
,表示某个作业正在进行中,并在作业完成后关闭该对话框。
问题是:标签未显示。这项工作似乎是在JDialog
完全布局之前开始的。
当我只是打开对话框而不等待作业并关闭时,标签显示为。
对话框的最后一件事是setVisible(true)
诸如revalidate()
,repaint()
之类的内容也无济于事。
即使我为受监视的作业启动一个线程,并使用someThread.join()
等待它也没有帮助,因为当前线程(即AWT线程)被join
阻止,我想。
用JDialog
替换JFrame
也无济于事。
那么,概念一般是错误的吗?或者我可以管理它以完成某项工作吗 确保JDialog
(或JFrame
)完全布局后?
我想要实现的简化算法:
JDialog
我设法编写了一个可重现的测试用例:
编辑现在解决了答案中的问题: 此用例会显示标签,但无法关闭 在“模拟过程”之后,由于对话的模态。
import java.awt.*;
import javax.swing.*;
public class _DialogTest2 {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
final JLabel jLabel = new JLabel("Please wait...");
@Override
public void run() {
JFrame myFrame = new JFrame("Main frame");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setSize(750, 500);
myFrame.setLocationRelativeTo(null);
myFrame.setVisible(true);
JDialog d = new JDialog(myFrame, "I'm waiting");
d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
d.add(jLabel);
d.setSize(300, 200);
d.setLocationRelativeTo(null);
d.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000); // simulate process
jLabel.setText("Done");
} catch (InterruptedException ex) {
}
}
});
d.setVisible(false);
d.dispose();
myFrame.setVisible(false);
myFrame.dispose();
}
});
}
}
答案 0 :(得分:4)
试试这个:
package javaapplication3;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Main {
public static void main(String[] args)
throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
final JLabel jLabel = new JLabel("Please wait...");
@Override
public void run() {
JFrame myFrame = new JFrame("Main frame");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setSize(750, 500);
myFrame.setLocationRelativeTo(null);
myFrame.setVisible(true);
JDialog d = new JDialog(myFrame, "I'm waiting");
d.add(jLabel);
d.setSize(300, 200);
d.setLocationRelativeTo(null);
d.setVisible(true);
new Thread(new Runnable() {
@Override
public void run() {
public void run() {
try {
Thread.sleep(3000); // simulate process
jLabel.setText("Done"); // HERE: should be done on EDT!
} catch (InterruptedException ex) {
}
}
}).start();
}
});
}
}
这个有效,但不正确。我会解释发生了什么。
您的main()
方法在'main'主题中开始。所有与Swing相关的代码都应该在EDT线程上完成。这就是你使用(正确)SwingUtilities.invokeAndWait(...)
的原因。到目前为止一切都很好。
但是在EDT上应该没有长时间运行的任务。由于Swing是单线程的,因此任何长时间运行的进程都将阻止EDT。所以你的代码Thread.wait(...)
永远不应该在EDT上执行。这是我的修改。我将调用包装在另一个线程中。所以这是Swing惯用的长期运行任务处理。我使用Thread类来简洁,但我真的建议使用SwingWorker
线程。
非常重要:我在前面的例子中犯了一个错误。看到“HERE”评论这一行?这是另一个Swing单线程规则违规。线程内的代码在外部 EDT运行,所以它永远不应该触摸Swing。因此,使用Swing单线程规则,此代码不正确。冻结GUI是不安全的。
如何纠正?简单。你应该将你的调用包装在另一个线程中并将其放在EDT队列中。所以正确的代码应如下所示:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jLabel.setText("Done");
}
});
编辑:这个问题涉及很多与Swing相关的问题。无法一次解释所有这些......但是这里还有一个片段,可以满足您的需求:
public static void main(String[] args)
throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
final JFrame myFrame = new JFrame("Main frame");
final JLabel jLabel = new JLabel("Please wait...");
final JDialog d = new JDialog(myFrame, "I'm waiting");
@Override
public void run() {
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setSize(750, 500);
myFrame.setLocationRelativeTo(null);
myFrame.setVisible(true);
d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
d.add(jLabel);
d.setSize(300, 200);
d.setLocationRelativeTo(null);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000); // simulate process
System.out.println("After");
SwingUtilities.invokeLater(new Runnable() {
public void run() {
d.setVisible(false);
d.dispose();
myFrame.setVisible(false);
myFrame.dispose();
}
});
} catch (InterruptedException ex) {
}
}
}).start();
d.setVisible(true);
}
});
}
总结一下:
SwingUtilities.
在EDT上运行代码...
invokeAndWait()
- 顾名思义,呼叫是同步的,invokeLater()
- 在某个时候调用代码,但立即返回Thread
(将Runnable传递给新的Thread或覆盖它的start()
方法)并启动它,SwingWorker
线程,其中包含一些额外内容。典型的GUI场景包括:
1.,2.,3。和4.在 EDT 上运行。 4.不应该。有很多方法可以编写正确的线程代码。最麻烦的是使用早期版本的Java附带的Thread
类。如果天真地做到这一点,就会浪费资源(一次运行的线程太多)。更新GUI也很麻烦。使用SwingWorker可以缓解这个问题。它保证在启动,运行和更新GUI时表现正常(每个都有一个专用的方法,你可以覆盖并确保它在适当的线程上运行)。
答案 1 :(得分:3)
如前面的答案中所述,你必须在EDT的一个不同的线程中运行冗长的工作。
最简单的恕我直言是使用SwingWorker并在完成后添加一个侦听器来处理对话框。这是一个示例:
SwingWorkerCompletionWaiter.java
public class SwingWorkerCompletionWaiter implements PropertyChangeListener {
private JDialog dialog;
public SwingWorkerCompletionWaiter(JDialog dialog) {
this.dialog = dialog;
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if ("state".equals(event.getPropertyName())
&& SwingWorker.StateValue.DONE == event.getNewValue()) {
dialog.setVisible(false);
dialog.dispose();
}
}
}
以下代码将执行一些冗长的任务:
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
// do something long
}
};
JDialog dialog = new JDialog();
// initialize the dialog here
worker.addPropertyChangeListener(new SwingWorkerCompletionWaiter(dialog));
worker.execute();
dialog.setVisible(true);
如果你想创建某种“请等待”对话框,可能更容易扩展JDialog并在所需的任何地方使用扩展类。
答案 2 :(得分:0)
首先,对于推测工作的事情感到抱歉,在发布代码之前,我并没有真正理解你想要做的事情。
尽管如此,我并不喜欢这种创建对话框和窗口的方式,所以我建议您为JDialog创建一个单独的类。只是一个简单的类,如下所示:
public class MyDialog extends JDialog
{
public MyDialog(JFrame owner, String title)
{
super(owner, title);
d.setSize(300, 200);
d.setLocationRelativeTo(null);
d.setVisible(true);
d.setDefaultCloseOperation(DISPOSE_ON_CLOSE );
Container c = getContentPane();
c.setLayout(new FlowLayout());
c.add(new JLabel("Please wait..."));
}
}
以这种方式启动对话框:
new MyDialog(this, "Your title");
它可能看起来像狗屎,但它可能有效(我还没有测试过)。
希望这有帮助。
答案 3 :(得分:0)
您需要在后台线程或主应用程序线程中运行作业。应使用SwingUtilities.invokeLater();
在事件调度线程(EDT)上创建JDialog和JFrame。然后等到作业完成并关闭(EDT)中的对话框。由于布局和作业位于两个独立的线程上,因此应该没有问题。实际上,您的示例的以下修改有效:
public static void main(String[] args) throws Exception {
final JFrame myFrame = new JFrame("Main frame");
final JDialog d = new JDialog(myFrame, "I'm waiting");
final Thread backgroundJob = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000); // simulate process
}
catch (InterruptedException ex) {
Logger.getLogger(NewClass.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
backgroundJob.start();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setSize(750, 500);
myFrame.setLocationRelativeTo(null);
myFrame.setVisible(true);
d.add(new JLabel("Please wait..."));
d.setSize(300, 200);
d.setLocationRelativeTo(null);
d.setVisible(true);
}
});
backgroundJob.join();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
d.setVisible(false);
}
});
}
答案 4 :(得分:0)
您应该使用invokeLater而不是InvokeAndWait。
public class DialogTest {
public static void main(String[] args) throws Exception {
final JLabel comp = new JLabel("the job has started");;
final JFrame myFrame = new JFrame("Main frame");
final JDialog d = new JDialog(myFrame, "I'm waiting");
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setSize(750, 500);
myFrame.setLocationRelativeTo(null);
myFrame.setVisible(true);
d.setModalityType(JDialog.ModalityType.APPLICATION_MODAL);
d.add(comp);
d.setSize(300, 200);
d.setLocationRelativeTo(null);
d.setVisible(true);
}
});
try {
Thread.sleep(3000);
// d.setVisible(false);
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
comp.setText("the job has finished");
d.setVisible(false);
d.dispose();
myFrame.setVisible(false);
myFrame.dispose();
}
});
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}