自定义Java启动画面“冻结”,直到整个应用程序加载完成

5

我有一个程序,加载时间很长。因此,我想开发一个启动画面,可以向用户提供正在加载的反馈信息。使用一个简单的JFrame,包含一张图片、一个标签和一个JProgressBar。

我一直在尝试,目前最好的结果是在我的main()方法中实现:

SwingUtilities.invokeAndWait(new Runnable() {

    public void run() {

        new SplashScreen();
    }
});

SwingUtilities.invokeAndWait(new Runnable() {

    public void run() {

        //Code to start system
        new MainFrame();
        //.. etc
    }
});

启动画面(SplashScreen)和主框架(MainFrame)都是继承JFrame类的。同时,我还使用了Substance作为库。

启动画面的构造函数将JLabel和JProgressBar添加到自己身上,然后进行打包并设置可见性。JProgressBar使用了setIndeterminate(true)方法;

当我运行程序时,启动画面会显示出来,但进度条被锁定了,它不会动,直到程序的其余部分启动后才开始按预期移动。

我错过了什么吗?我所查找的所有内容似乎都没有提到这个问题,大多数“自定义启动画面”的实现方式与我的非常相似。

4个回答

3

invokeAndWait会冻结Swing线程,知道它接收到的操作完成。因此,直到MainFrame被创建前,进度条将完全被冻结。

你应该生成一个非Swing线程,使用SwingUtilities.invoke(Later|AndWait)来更新闪屏界面。

new Thread(new Runnable() { public void run() {
  // Create MainFrame here
  SwingUtilities.invokeLater(new Runnable() {
      public void run() {
         updateProgressHere();
      }
  });

  // Do other initialization
  SwingUtilities.invokeLater(new Runnable() {
      public void run() {
         updateProgressHere();
      }
  });
}).start(); 

谢谢您的回复,经过一番尝试,我已经让它正常工作了,但是没有使用Substance。当我使用您的代码时,我遇到了以下问题:SwingUtilities.invokeLater(new Runnable() {public void run() { try { UIManager.setLookAndFeel(new SubstanceSaharaLookAndFeel()); } catch (UnsupportedLookAndFeelException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } }}); 我收到了“必须在事件分派线程上完成组件创建”的错误提示。 - Andy Smith
1
Substance 强制执行组件创建线程规则 - 你必须在 Swing EDT 上创建所有 GUI 组件。请查看我的回答。 - Russ Hayward

3

为了比Allain的正确答案更加通俗易懂,我来尝试一下。

在您的系统中有一个线程可以(合法地)更新GUI。

如果您使用该线程来初始化系统,则GUI将不会更新。

如果您尝试从主线程更新GUI,则不会成功。

始终注意GUI所在的线程,并确保不要在该线程上执行任务。

可以这样考虑GUI更新:

  • 非GUI线程更新您正在显示的值(例如完成百分比)。
  • 它通过invokeLater发布更新,然后继续其他工作。
  • GUI线程执行您的invokeLater,使用从非GUI线程传递的数据更新GUI
  • GUI线程退出!这允许系统的其他不相关部分在需要时更新GUI。

非GUI线程通常是Thread.start和传递给“main”的线程。

GUI线程是执行您的“invokeLater”和任何Swing事件(按下按钮,GUI计时器,任何Swing侦听器)的线程。

因此,大多数情况下,在更新GUI时您已经在GUI线程上运行 - 只需注意启动顺序和线程想要更新显示的位置即可。


1
其他答案已经介绍了大部分,但简要来说,您的问题是在Swing事件调度线程中运行“启动系统代码”。所有与GUI相关的代码(包括组件创建)必须在EDT上运行,但所有其他代码不应在EDT上运行。请尝试更改您的程序以执行此操作:
SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
        new SplashScreen();
    }
});
// Code to start system (nothing that touches the GUI)
SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
        new MainFrame();
    }
});
//.. etc

1

其他人已经提到了为什么你的进度条被阻塞,我建议一个替代方案。Java6内置了启动画面处理到javaw和java启动器中...

启动画面甚至在JVM和应用程序类加载之前就显示出来,然后一旦启动,应用程序可以更新它以反映其内部加载状态,并在准备好时关闭它。

或者,对于仅限Windows的解决方案,Winrun4J java启动器也具有启动画面功能。


内置的启动画面只是一张图片,因此无法用于显示加载进度条。 - Powerlord
实际上不是,从我的链接中可以看到:“SplashScreen类用于...在启动画面中绘制。” - RedPandaCurios

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接