Swing Splash screen with progress bar
invokelater, java, jprogressbar, splash-screen, swing
Solution
An `invokedLater` is invoked from another `invokeLater`. The `runnable` will be executed only when the execution of the first one is finished.
You should modify the Splashscreen code like this :
...
private void runInEdt(final Runnable runnable) {
if (SwingUtilities.isEventDispatchThread())
runnable.run();
else
SwingUtilities.invokeLater(runnable);
}
public void showScreen() {
runInEdt(new Runnable() {
public void run() {
setVisible(true);
}
});
}
public void close() {
runInEdt(new Runnable() {
public void run() {
setVisible(false);
dispose();
}
});
}
public void setProgress(final String message, final int progress) {
runInEdt(new Runnable() {
public void run() {
progressBar.setValue(progress);
if (message == null)
progressBar.setStringPainted(false);
else
progressBar.setStringPainted(true);
progressBar.setString("Loading " + message + "...");
}
});
}
Problem
This is my splash screen code, ``` public class SplashScreen extends JWindow { private static final long serialVersionUID = 1L; private BorderLayout borderLayout = new BorderLayout(); private JLabel imageLabel = new JLabel(); private JProgressBar progressBar = new JProgressBar(0, 100); public SplashScreen(ImageIcon imageIcon) { imageLabel.setIcon(imageIcon); setLayout(borderLayout); add(imageLabel, BorderLayout.CENTER); add(progressBar, BorderLayout.SOUTH); pack(); setLocationRelativeTo(null); } public void showScreen() { SwingUtilities.invokeLater(new Runnable() { public void run() { setVisible(true); } }); } public void close() { SwingUtilities.invokeLater(new Runnable() { public void run() { setVisible(false); dispose(); } }); } public void setProgress(final String message, final int progress) { SwingUtilities.invokeLater(new Runnable() { public void run() { progressBar.setValue(progress); if (message == null) { progressBar.setStringPainted(false); } else { progressBar.setStringPainted(true); } progressBar.setString("Loading " + message + "..."); } }); } } ``` From the main method I am invoking like this, ``` public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { try { UIManager.setLookAndFeel(UIManager .getSystemLookAndFeelClassName()); SplashScreen splashScreen = new SplashScreen(new ImageIcon("images/splash.jpg")); splashScreen.showScreen(); AppFrame frame = new AppFrame(splashScreen); } catch (Exception e) { appLogger.error(e.getMessage(), e); } } }); } ``` In the constructor of AppFrame I call, splashScreen.setProgress(msg, val) method to update the progress bar. But the splash is not showing. It is showing only at the end when the frame is displayed only for a fraction of second even though the load takes much long time. But if I put these three lines ``` SplashScreen splashScreen = new SplashScreen(new ImageIcon("images/splash.jpg")); splashScreen.showScreen(); AppFrame frame = new AppFrame(splashScreen); ``` outside the invokeLater() the splash screen is shown and the progress bar updates nicely. I believe GUI updates should be in invokeLater. What could be the problem? Btw, AppFrame loads various panels of my application. Edit: A mock of my AppFrame is shown below. ``` public class AppFrame extends JFrame { public AppFrame(SplashScreen splashScreen) { JPanel test = new JPanel(); test.setLayout(new GridLayout(0, 10)); splashScreen.setProgress("jlabel", 10); for(int i = 0; i < 10000; i++) { test.add(new JButton("Hi..." + i)); splashScreen.setProgress("jbutton", (int)(i * 0.1)); } add(new JScrollPane(test)); setPreferredSize(new Dimension(800, 600)); pack(); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); setLocationRelativeTo(null); splashScreen.setProgress("complete", 100); setVisible(true); } } ```