我制作了很多不同的 Swing 应用程序,它们的加载时间通常在几秒到几分钟之间变化,具体取决于应用程序 UI/数据大小。此外,在某些情况下,应用程序数据加载与 UI 加载混合。
几秒钟的加载时间不是问题,但是当它超过 10 秒时 - 很明显应该显示某种加载屏幕,直到 UI/数据完全初始化。
您通常会做什么 - 首先创建某种加载屏幕(例如,带有徽标的窗口和一些在应用程序加载时正在更新的标签),然后从应用程序中的各种加载“点”更新它。
问题是 - 应用程序通常在排队进入 EDT 的单个调用中加载,并且很难将其分成对 EDT 的多次调用而不会使应用程序的代码复杂化。因此,由于应用程序加载是在排队到 EDT 的单个调用中执行的,因此您根本无法正确更新加载屏幕 - 由于 EDT 忙于加载应用程序,因此在应用程序初始化之前不会显示更新。
因此,为了在某些情况下实现加载屏幕,我已将应用程序 UI 初始化移到 EDT 之外,并将它们设计为在执行加载时不会执行任何 UI 更新内容。应用程序的框架显示和所有应用程序 UI 操作仍将在 EDT 中执行。这通常不太好,但经过大量测试和查看 Swing 代码后,我确信即使在大型应用程序中它也不会导致任何问题。尽管如此,即使它不会引起任何问题,这通常也不是一件好事。
所以问题是:在 EDT 中保持应用程序初始化的同时,可以使用哪些方法来正确显示和更新应用程序加载屏幕?
希望它不是太宽泛。
这是一个展示“坏”方法的“虚拟”应用程序:
import javax.swing.*;
import java.awt.*;
public class DummyApplication extends JFrame
{
private static JDialog loadingDialog;
private static JLabel loadingProgress;
public DummyApplication ()
{
super ( "Dummy application" );
dummyProgressUpdate ( "Loading content...", 3000 ); …Run Code Online (Sandbox Code Playgroud) 我有一个GUI,它在运行它的平台上构建/初始化非常繁重.因此我想在初始化时更新进度..
我有一个小的未修饰的JDialog包含一个JLabel和一个JProgressBar,我想在初始化期间在特定的地方更新,但是,因为事件调度thead(根据Swing规则)用于构建/初始化GUI,进展当然是直到EDT再次空闲(即初始化完成)才更新..
JProgressBar我已经使用"paintImmediately"重绘了,但我似乎无法让它适用于JLabel和对话框本身..是否有任何简单的推荐/证明方法来实现这一目标?
干杯...
编辑:添加一个我正在尝试做的例子; 当然,大大简化了.
private JLabel progressLabel;
private JProgressBar progressBar;
public static int main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showProgressDialog();
progressLabel.setText("construct 1");
constructSomeHeavyGUI();
progressLabel.setText("construct 2");
progressBar.setValue(33);
constructSomeMoreHeavyGUI();
progressLabel.setText("construct 3");
progressBar.setValue(67);
constructEvenMoreHeavyGUI();
progressLabel.setText("done");
progressBar.setValue(100);
hideProgressDialog();
showHeavyGUI();
}
});
}
Run Code Online (Sandbox Code Playgroud)
只要EDT忙碌,并且在我们全部完成而不是一路更新之后,由progressBar.setValue()/ progressLabel.setText()上面的呼叫引起的重新绘制当然会排队等待.
这个问题与我在这里问的问题有些关系.现在,我有一个类"Controller",它由main方法和所有swing组件组成.有一个名为"VTOL"的类,它由一个名为"altitude"的变量组成(我现在声明这个变量是volatile).
这是一个由在后台运行的线程组成的类:
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Vineet
*/
public class Gravity extends Thread {
String altStr;
double alt;
Controller ctrl = new Controller();
@Override
public void run() {
while (true) {
alt=VTOL.altitude;
System.out.println(alt);
alt = alt-0.01;
VTOL.altitude= (int) alt;
altStr=new Integer(VTOL.altitude).toString();
ctrl.lblAltitude.setText(altStr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
首先,我最初面临的问题是我无法更新"高度"的值,它在整个程序执行过程中保持为0.所以我宣称它是不稳定的(我不知道它是否是一个好习惯)
其次,在Controller类中有一个名为"lblAltitude"的jLabel,我希望将其值更新为此线程中的更改,但不知何故那不会发生.我怎样才能做到这一点?
我仍然是Java的初学者,所以我没有学到很多关于线程和并发的知识.但是,我希望能够使用ScheduledThreadPoolExecutor作为计时器,因为我遇到了java.util.Timer和TimerTask的问题.我对线程的创建非常感兴趣,并且知道我将在几周内学习它们.但是,如果有可能,有人可以给我一个基本的例子,说明如何使用util.timer将当前的迷你测试程序转换为使用ScheduledThreadPoolExecutor?
我想尽快完成这个例子,所以我没有太多时间去学习线程 - 不管我想要多少.说完这些之后,请包含您认为Java初学者应该了解的有关ScheduledThreadPoolExecutor的重要信息.
示例程序
我已经做了一个快速的小例子来表示我在一个更大的程序中遇到的问题.该程序应该做的是允许用户按下按钮来启动计数器.然后,用户必须能够在他/她想要时停止并重新启动计数器.在较大的程序中,此计数器保持相等至关重要,因此我使用了scheduleAtFixRate()方法.初始延迟始终相同(在这种情况下为0)也很重要.问题(我相信你会看到)是一旦取消定时器就无法重启 - 我希望ScheduledThreadPoolExecutor能解决这个问题.
码:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.TimerTask;
import java.util.Timer;
public class Tester extends JFrame {
JButton push = new JButton("Push");
static JTextArea textOut = new JTextArea();
Timer timer = new Timer();
boolean pushed = false;
static int i = 1;
public Tester() {
super();
add(push, BorderLayout.NORTH);
add(textOut);
push.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!pushed) {
timer.scheduleAtFixedRate(new Task(), 0, 1000);
pushed = true;
} else {
timer.cancel();
pushed …Run Code Online (Sandbox Code Playgroud) java swing multithreading executorservice event-dispatch-thread
在我的工具中,我记录了从用户应用程序生成的事件.现在,用户可以使用我的工具重播他的场景.
在重放用户录制的场景时,我使用以下命令逐个将录制的事件发布到"EventQueue":
eventQueueObj.postEvent(eventObj);
Run Code Online (Sandbox Code Playgroud)
在大多数情况下,重放用户的录制场景没有问题.
为了解释我的问题,我将举一个简短的例子.
我的工具的用户想检查在他的应用程序中,"文件"菜单下的"关闭"按钮是否有效.他的情景是:
1)单击"文件"菜单.
2)单击"打开菜单项"
3)从文件选择器中选择文件.
4)单击"文件选择器"上的"打开".
5)打开文件后,单击"文件"菜单.
6)单击"关闭菜单项".
重放步骤1到步骤4没有问题.现在在步骤5,我应该等到文件打开(文件被打开非常大并且需要一些时间).如果我不等到文件打开并向前移动,"文件"菜单下的"关闭"菜单项将保持禁用状态.因此,不会在"关闭"菜单项上触发鼠标单击事件.
我的问题是"如何从Event Dispatch Thread获取已完成当前事件处理的信息?"
在此之后,我可以点击"文件"菜单,然后点击步骤6.
我不想使用sleep()来递归检查是否启用了"关闭"菜单项.因为我只想等待所需的时间而不是一些大概的时间.
如果我使用sleep(),在大多数情况下,即使用户的文件打开操作完成,我也会浪费一些执行时间.
很确定它是这样的 - 但我想确定地知道 - 在 invokeLater() 或 invokeAndWait() 的情况下给出的关系是发生在之前吗?
这些方法在(分别为 SwingUtilities)AWT.EventQueue 中定义。我想当在 EventQueue 中输入一些东西时会涉及同步,因此作为同步的结果,发生了关系,最后给出了可见性。
但真的是这样吗?(我在哪里可以找到这些信息?)
例如在一些工作线程内
...
*<1> heavy computation modifying lots of DATA*
...
SwingUtilities.invokeLater(
new Runnable() {
@Override
public void run() {
*<2> is visibility of modified DATA guaranteed?*
}
}
);
Run Code Online (Sandbox Code Playgroud)
例如在一些线程内
...
SwingUtilities.invokeAndWait(
new Runnable() {
@Override
public void run() {
...
*<1> change some DATA*
}
}
);
*<2> is visibility of modified DATA guaranteed?*
Run Code Online (Sandbox Code Playgroud) SwingWorker javadoc中提到了上述语句.
在一个应用程序中,我看到一个冗长的后台任务在一个不同的线程中运行并且更新UI也没有问题(可以访问对Swing组件的引用).
难道会发生什么坏事吗?
Swing需要在Event Dispatch Thread(EDT)中运行.如何在Spring上下文中确保这一点?
在一些教程中,像这样,swing组件只是像普通bean一样实例化.这个可以吗?
现在学习一点 Swing,发现两个教程,它们使用不同的方法来制作简单的 JFrame 窗口。
第一个实现 Runnable 并在类中有一个 JFrame 对象变量:
class SwingDemo implements Runnable {
private JFrame frame;
@Override
public void run() {
frame = new JFrame("title");
... // setSize(), add components, etc
}
}
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new SwingDemo());
}
}
Run Code Online (Sandbox Code Playgroud)
第二个教程没有实现Runnable,而是使用类构造函数来初始化JFrame,并通过匿名内部类调用构造函数
class SwingDemoAlt {
public SwingDemoAlt() {
JFrame frame = new JFrame("title");
... // again some code here
}
}
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() …Run Code Online (Sandbox Code Playgroud) 由于某种原因,当我调用SecondaryLoop.enter()AWT 事件调度线程 (EDT) 时,它不会等待SecondaryLoop.exit()被调用才解除阻塞。
由于我认为SecondaryLoop不是一个非常知名的课程,我简单介绍一下:
一般来说,在 EDT 上运行任何长时间执行或阻塞的代码是一个坏主意,因为这样您的应用程序将不会响应任何事件,直到该代码终止。允许EventQueue.createSecondaryLoop()您创建一个新的事件循环来处理事件,从而允许您在不损失响应能力的情况下阻止 EDT。这就是 Swing 模式对话框用来允许您在等待对话框关闭时阻止 EDT,但仍然允许对话框本身的控件能够运行。
创建SecondaryLoop实例后,您应该能够调用enter()并且它应该阻塞直到exit()被调用。
来自文档
此方法可以由任何线程(包括事件分派线程)调用。该线程将被阻塞,直到调用 exit() 方法或终止循环。在任何一种情况下,都会在事件分派线程上创建一个新的辅助循环来分派事件。
不过,我不完全确定“或循环终止”时的含义。这可能是我的问题。
enter()在 EDT 之外的线程上调用该方法会按照我的预期阻塞:
System.out.println("Enter Loop");
Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop().enter();
System.out.println("Done (we should never get here)");
Run Code Online (Sandbox Code Playgroud)
输出:
Enter Loop
Run Code Online (Sandbox Code Playgroud)
但是,如果我们在 EDT 上调用它,它会阻塞大约一秒钟,然后继续:
Enter Loop
Run Code Online (Sandbox Code Playgroud)
输出:
Enter Loop
Done (we should never get here)
Run Code Online (Sandbox Code Playgroud)
根据 tevemadar 的评论(谢谢顺便说一句),我更新了代码以防止任何可能的垃圾收集问题:
System.out.println("Enter Loop");
try {
SwingUtilities.invokeAndWait(() -> Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop().enter());
} catch (InvocationTargetException | InterruptedException …Run Code Online (Sandbox Code Playgroud)