7

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);
}
}
Praveen
  • 522
  • 1
  • 8
  • 17
  • 1
    *" I believe GUI updates should be in invokeLater."* Or `invokeAndWait`. But do some logging, I expect you will find the times between completion of `splashScreen.showScreen();` and `AppFrame frame = new AppFrame(splashScreen);` is much less than you expect. Also note that the best splashes are strictly AWT (no Swing). – Andrew Thompson Aug 13 '12 at 12:49
  • That means I should use Window or Frame instead of JWindow? Thanks. – Praveen Aug 13 '12 at 13:25
  • Yes, use `Window` or `Frame`. Also look into `MediaTracker`/`Canvas` & `EventQueue` - there must be **no** Swing imports or classes used, or the entire Swing package is loaded 1st. – Andrew Thompson Aug 13 '12 at 14:32
  • Also consider a [tag:java-web-start] splash screen. – trashgod Aug 13 '12 at 15:37
  • hmm ... is it really the ui creation that takes so long? If not, extract the _real_ time-hogs (!ui = !EDT) and prepare them in the doInBackground of the the SwingWorker, publishing intermediate progress to the splash and close splash/show app in done – kleopatra Aug 14 '12 at 10:29
  • @kleopatra Should EDT be used for all swing related works including add(comp), new JPanel() etc or only for setVisible(), repaint() etc – Praveen Aug 14 '12 at 10:49
  • see http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html - but I doubt that the creation of the ui _really_ is the bottleneck – kleopatra Aug 14 '12 at 11:12
  • @kleopatra Thanks. But if you look at the mock AppFrame code which I provided, it does take some time to load (some 10000 objects) and the progress bar not updating. What do you think could be the problem? Meanwhile, I will also find the exec times in my real code as well. – Praveen Aug 14 '12 at 12:02
  • why do you need tens of thousands of components? That's not the usual real-world scenario ... – kleopatra Aug 14 '12 at 12:37

3 Answers3

5

Hmm take a look at this working sample I put together which uses the SplashScreen class (only uses a simple Timer and ActionListener to increase the ProgressBar's value until 100 and a frame is shown):

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionListener;
import javax.swing.*;

public class SplashScreen extends JWindow {

    private static JProgressBar progressBar = new JProgressBar();
    private static SplashScreen splashScreen;
    private static int count = 1, TIMER_PAUSE = 25,PROGBAR_MAX=100;
    private static Timer progressBarTimer;
    ActionListener al = new ActionListener() {

        @Override
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            progressBar.setValue(count);
            System.out.println(count);
            if (PROGBAR_MAX == count) {
                splashScreen.dispose();//dispose of splashscreen
                progressBarTimer.stop();//stop the timer
                createAndShowFrame();
            }
            count++;//increase counter

        }
    };

    public SplashScreen() {
        createSplash();
    }

    private void createSplash() {
        Container container = getContentPane();

        JPanel panel = new JPanel();
        panel.setBorder(new javax.swing.border.EtchedBorder());
        container.add(panel, BorderLayout.CENTER);

        JLabel label = new JLabel("Hello World!");
        label.setFont(new Font("Verdana", Font.BOLD, 14));
        panel.add(label);

        progressBar.setMaximum(PROGBAR_MAX);
        container.add(progressBar, BorderLayout.SOUTH);


        pack();
        setLocationRelativeTo(null);
        setVisible(true);

        startProgressBar();
    }

    private void startProgressBar() {
        progressBarTimer = new Timer(TIMER_PAUSE, al);
        progressBarTimer.start();
    }

    private void createAndShowFrame() {
        JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                splashScreen = new SplashScreen();
            }
        });
    }
}
David Kroukamp
  • 36,155
  • 13
  • 81
  • 138
2

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 + "...");
        }
    });
}
gontard
  • 28,720
  • 11
  • 94
  • 117
  • I tried your solution. Since it is already in EDT, else part in 'runInEdt' will not execute. Anyways I still have the same problem. The progress bar is not getting updated in EDT. I used this as an [example](http://www.devdaily.com/java/java-splash-screen-with-progress-bar-4) and just added invokeLater in main(). Without invokeLater it works fine. – Praveen Aug 13 '12 at 17:37
  • The 'runInEDT' method ensures that your Splashscreen is thread safe. It can be invoked from any thread. I don't understand why my solution doesn't works for you. May be you could provide more details on the content of the AppFrame constructor. – gontard Aug 14 '12 at 07:39
  • Pls see the edit which has a mock of my AppFrame. Basically I am loading my entire app components in that constructor. So I want to update the progress bar while the components load. But it is not showing even though it takes quite a time to load. – Praveen Aug 14 '12 at 08:33
  • 1
    Are you sure this mock behaviour is equivalent with the real one ? I mean, generally the loading time is not due to the gui initialization. – gontard Aug 14 '12 at 12:42
  • Thanks it was due to one team member's dao code running in EDT. – Praveen Aug 15 '12 at 03:52
0
    Thread l_splash_thread = new Thread(
            new SplashScreen ());
    l_splash_thread.start();

    try {
        l_splash_thread.join();
    } catch (InterruptedException e1) {
        e1.printStackTrace();
    }

I think this way you can hold your splash scrren.

Java_Alert
  • 1,159
  • 6
  • 24
  • 50