Search code examples
javaswingsplash-screenjprogressbarinvokelater

Swing Splash screen with progress bar


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);
}
}

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 + "...");
            }
        });
    }