1

the code below is the implementation of a splash screen for a small Java application I have developed with Eclipse. The splash screen works perfectly well on a PC but not on a MAC. On MAC OSX, the frame first appears has a gray area during 2 seconds and then the image appears for the remaining of the 4 seconds. The image should normally appears right away and for a duration of 4 seconds. Do you have any idea why there is a delay before the image appears on a MAC while everything works well on a PC? PS: I have deployed the application as an executable Jar and I'm using Java 8 on all computers. Thank you.

public static void main(String[] args)
{
    SplashScreen splSplashScreen = new SplashScreen();
    //Main window
    FenetrePrincipale fenetrePrincipale = new FenetrePrincipale();
}
public class SplashScreen extends JWindow
{
    /**
     * Numéro de série
     */
    private static final long serialVersionUID = 1592663893301307318L;

    private final static long TEMP_AFFICHAGE = 4000;

    /**
     * Constructeur par initialisation
     * @param p_Frame Frame
     * @param p_TempsAffichage Temps d'affichage en millisecondes
     */
    public SplashScreen()
    {
        super(new Frame());
        JLabel lblImage = new JLabel(new ImageIcon(this.getClass().getResource("/res/ui/splashScreen.jpg")));

        Container container = this.getContentPane();
        container.add(lblImage, BorderLayout.CENTER);
        pack();

        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension labelSize = lblImage.getPreferredSize();
        this.setLocation(screenSize.width/2 - (labelSize.width/2), screenSize.height/2 - (labelSize.height/2));

        this.setVisible(true);

        try
        {
            Thread.sleep(TEMP_AFFICHAGE);
        }
        catch (InterruptedException ex)
        {
            ApplicationLogger.getInstance().severe(ex.getLocalizedMessage());
        }
        finally
        {
            this.setVisible(false);
        }
    }
}
  • The `Thread.sleep()` causes the java application to stop processing messages; which means that the UI doesn't get updated. Follow the premiss in [this answer](http://stackoverflow.com/questions/16288303/best-example-for-programmatically-creating-splashscreen-with-text) which should help you in creating the worker appropriately. – Anya Shenanigans Nov 02 '16 at 14:26
  • But the image appears before the end of the Thread.sleep(). And, the Thread.sleep() should not stop the image to load because the image is loaded before the Thread.sleep() (this.setVisible(true);). – andré marquis Nov 02 '16 at 16:40
  • It stops repaints which is what is causing the issue. When you block the UI thread, these things happen. The solution is to *not block the UI thread* – Anya Shenanigans Nov 02 '16 at 16:42
  • Ok, so should I put the Thread.sleep() in a more appropriate place? If not, how could I make the picture disappears after 4 seconds? I don't wish to implement the SplashScreen API. I'm searching for a very simple solution. I just want to display a picture during 4 seconds before the application is launched. Thank you – andré marquis Nov 02 '16 at 17:06

1 Answers1

2

Edit This is completely different from the original answer; I'm leaving the original answer below.

It looks like the initial JLabel render is dog slow on OSX - removing all the sleeps still leaves me with a ~2 second pause while java renders the label. So we change the rules.

First we create a JPanel class that takes a buffered image:

class ImgPanel extends JPanel {
    private BufferedImage img;

    public ImgPanel(BufferedImage img) {
        this.img = img;
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(img.getWidth(), img.getHeight());
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img, 0, 0, null);
    }
}

We then change the splash screen constructor like so:

public SplashScreen()
{
    BufferedImage img = null;
    try {
        img = ImageIO.read(this.getClass().getResource("/res/ui/splashScreen.jpg"));
    } catch (Exception ex) {
    }

    ImgPanel panel = new ImgPanel(img);
    Container container = this.getContentPane();
    container.add(panel);
    pack();

    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    this.setLocation(screenSize.width/2 - (img.getWidth()/2), screenSize.height/2 - (img.getHeight()/2));

    this.setVisible(true);
 }

This renders the image as soon as the panel appears without a pause.

Note - I've removed the entire sleep code to reduce confusion - the logic would be better captured in a separate SwingWorker to perform the hide of the splash screen and the display of the main screen:

class worker extends SwingWorker<String, Object> {
    private final static long TEMP_AFFICHAGE = 4000;
    private SplashScreen splash;
    private FenetrePrincipale principale;

    public worker(SplashScreen splash, FenetrePrincipale principale) {
        this.splash = splash;
        this.principale = principale;
        this.splash.setVisible(true);
    }

    @Override
    public String doInBackground() {
        try
        {
            Thread.sleep(TEMP_AFFICHAGE);
        }
        catch (InterruptedException ex)
        {
            ApplicationLogger.getInstance().severe(ex.getLocalizedMessage());
        }
        return "";
    }

    @Override
    protected void done() {
        splash.setVisible(false);
        principale.setVisible(true);
    }
};

Then the main code looks like:

public static void main(String[] args)
{
    SplashScreen splSplashScreen = new SplashScreen();
    //Main window
    FenetrePrincipale fenetrePrincipale = new FenetrePrincipale();
    worker w = new worker(splSplashScreen, fenetrePrincipale);
    w.execute();
}

Original answer - putting the thread for sleeping into a SwingWorker is still a good idea, as it would allow you to perform actual work before initialization.

Ok, a simple example of putting the sleep off the gui thread, using your splash code - this code comes after the this.setVisible(true), and replaces the try {} catch {} finally {} clause:

    this.setVisible(true);

    worker do_work = new worker(this);
    do_work.execute();
}

class worker extends SwingWorker<String, Object> {
    private SplashScreen parent;

    public worker(SplashScreen parent) {
        this.parent = parent;
    }

    @Override
    public String doInBackground() {
        try
        {
            Thread.sleep(TEMP_AFFICHAGE);
        }
        catch (InterruptedException ex)
        {
            ApplicationLogger.getInstance().severe(ex.getLocalizedMessage());
        }
        return "";
    }

    @Override
    protected void done() {
        parent.setVisible(false);
    }
};

I just create a SwingWorker which does a sleep as doWorkInBackground, and once that is concluded, it closes the parent frame, which is the splash screen.

Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122
  • As written, the main window can open while the splash screen is still visible. If you want to defer the main window from becoming visible until the after the splash screen goes away, then you would need to put the call into the 'done' part of the SwingWorker. This could be relatively easily handled by moving the SwingWorker into the main class so that it would have a reference to both the splash screen and the main window; allowing it to control the open of one and the close of the other. – Anya Shenanigans Nov 03 '16 at 08:11
  • I have implemented your SwingWorker class exactly has you told and no improvement. It didn't solve the problem at all on a Mac OSX (but it works on a PC). There is still a gray window during about 2 seconds after I double-click the Jar file then the image appears for the remaining of the 4 seconds of the Thread.Sleep(). So, the problem is not that I'm blocking the UI thread (after all, all the solutions work on a PC). It only doesn't work on Mac OSX. If you have another ideas, please post a message! Thank you! – andré marquis Nov 03 '16 at 12:36
  • The initial grey window is while the JVM renders the image in the JLabel. It's got nothing to do with the thread sleep in this case particularly. I'll change my answer to use something lighter like a jpanel. – Anya Shenanigans Nov 03 '16 at 13:55
  • Thank you! I tried your solution and I don't know what's wrong but the splash screen's image doesn't appear. There's only a tiny black square on the screen that appears. Is it normal that you don't call the paintComponent method of your ImgPanel class? – andré marquis Nov 03 '16 at 15:31
  • Ok! I got it. The @Override statement was missing for the paintComponent and getPreferredSize methods in the ImgPanel class. Now the image appears correctly. I let you know if it works on OS X. Thank you! – andré marquis Nov 03 '16 at 15:38
  • Huh, that's odd that you require the `@Override` to get it to work properly. I've created a simple [github project](https://github.com/petesh/splishy) with all the code that I've been tinkering with for this - there would be differences in this as opposed to your code - e.g. the path to the splash image. – Anya Shenanigans Nov 03 '16 at 16:15
  • I tested it on Mac OS X and it works perfectly! Thank you very much, you have my vote! :) – andré marquis Nov 03 '16 at 16:30