views:

476

answers:

4

I have a program that takes a long time to load. Because of this I wanted to develop a splash screen that can provide feedback to the user on what is being loaded. A simple JFrame with an image, label and JProgressBar.

I have been experimenting and the best results I've had are doing this in my main():

SwingUtilities.invokeAndWait(new Runnable() {

    public void run() {

        new SplashScreen();
    }
});

SwingUtilities.invokeAndWait(new Runnable() {

    public void run() {

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

Both SplashScreen and MainFrame are classes extending JFrame. I am also using Substance as a Library.

SplashScreen's constructor adds a JLabel and JProgressBar to itself, packs and sets visible. The JProgressBar is setIndeterminate(true);

When I run my program, my SplashScreen is displayed but the ProgressBar is locked, it doesn't move, not until the rest of the program has started does it start moving as expected.

What am I missing here? All searching I've done doesn't seem to mention this problem and most "custom splash screen" implementations go about it a very similar way to myself.

+3  A: 

invokeAndWait will freeze the Swing thread until the operation it is given completes. So, your progress bar will be 100% forzen until the MainFrame is created.

You should spawn a non-swing thread that updates the Splash Screen using 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(); 
Allain Lalonde
Thanks for the response, after a bit of fiddling I have this working but only without using Substance. When, using your code, I have: 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); } }}); I get the error "Component creation must be done on Event Dispatch Thread"
Andrew
Substance enforces component creation threading rules - you must create all GUI components on the Swing EDT. See my answer.
Russ Hayward
+1  A: 

Others have mentiones why your progress bar is blocked, I will suggest an alternative solution Java6 has splash screen handling built in to the javaw and java launchers...

The splash screen is displayed even before the JVM and the application's classes are loaded, and then once started the application can update it to reflect its internal loading state, and close it when ready.

Alternatively, for windows-only solutions, the Winrun4J java launcher also has a splash screen feature.

CuriousPanda
The built-in Splashscreen is just an image, and thus is useless for displaying loading progress bars.
R. Bemrose
actually no, from my link: "The SplashScreen class is used to ... paint in the splash screen."
CuriousPanda
+3  A: 

Just to attempt to be a little more general than Allain's correct answer.

There is one thread in your system that can (legally) update GUIs.

If you are using that thread to initialize your system, guis will NOT update.

If you are trying to update your GUI from your main thread, nothing will go right.

Always be aware of which thread your GUI is on and be sure not to be doing work on that thread.

Think of GUI updates in this way:

  • your non-gui thread updates a value you are displaying (say the percent done).
  • it posts an update via invokeLater, then continues with other work.
  • The GUI thread executes your invokeLater which updates your GUI with the data passed from the non-GUI thread
  • the GUI thread it exits! This allows other unrelated parts of the system to update the GUI when they need to.

Non-GUI threads are most often threads from Thread.start and the one passed to "main"

The GUI thread is the one executing your "invokeLater" and anything passed to you from a GUI event (button pressed, GUI timer, any Swing listeners.)

Because of this, most often you are already on the GUI thread when it's time to update the GUI--just watch out for startup sequencing and places where threads want to update the display.

Bill K
+1  A: 

Other answers have covered most of this but in brief your problem is that you are running the "Code to start system" in the Swing event dispatch thread. All GUI-related code (including component creation) must be run on the EDT but all other code should not be run on the EDT. Try changing your program to do this:

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
Russ Hayward