Search code examples
javamultithreadingswingsplash-screencountdownlatch

How to show a splash-screen, load datas in the background, and hide the splash-screen after that?


I'm designing a simple JavaFX form.

First, I load the JavaFX environment (and wait for it to finish), with something like this :

final CountDownLatch latch_l = new CountDownLatch(1);
try {
    // init the JavaFX environment
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            new JFXPanel(); // init JavaFX
            latch_l.countDown();
        }
    });
    latch_l.await();
}

This works fine. (the reason why I need to first load the JavaFX this way, is because it's mainly a Swing application, with some JavaFX components inside, but they are loaded later)

Now, I'd like to add a splash-screen on launch, and displays it while the JavaFX environment loads (and in fact put in on-screen for like 5 seconds, because there are logo, trademark etc.. of the application I need to show)

So I came up with a SplashScreen class, which just displays a JWindow on-screen, like that :

public class SplashScreen {

    protected JWindow splashScreen_m = new JWindow();
    protected Integer splashScreenDuration_m = 5000;

    public void show() {
        // fill the splash-screen with informations
        ...

        // display the splash-screen
        splashScreen_m.validate();
        splashScreen_m.pack();
        splashScreen_m.setLocationRelativeTo(null);
        splashScreen_m.setVisible(true);
    }

    public void unload() {
        // unload the splash-screen
        splashScreen_m.setVisible(false);
        splashScreen_m.dispose();
    }
}

Now, I want for the splash-screen to load and display itself 5 seconds. Meanwhile, I want the JavaFX environment to load, too.

So I updated the CountDownLatch like this :

final CountDownLatch latch_l = new CountDownLatch(2); // now countdown is set to 2

final SplashScreen splash_l = new SplashScreen();

try {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            // show splash-screen
            splash_l.show();
            latch_l.countDown();

            // init the JavaFX environment
            new JFXPanel(); // init JavaFX
            latch_l.countDown();
        }
    });
    latch_l.await();
    splash_l.unload();
}

So, it's working, but the splash only stays for the JavaFX environment to load, so basically it unloads very quickly (which is normal, given the code I wrote).

How to display the splash-screen for 5 seconds minimum (if the JavaFX loads faster) without freezing the EDT ?

Thanks.


Solution

  • The most significant issue is you're blocking the Event Dispatching Thread, meaning that it can't display/update anything while it's blocked. The same problem applies to JavaFX.

    You should, also, never update either from anything other then they respective event queues.

    Now, there are any number of ways you might be able to go about this, but SwingWorker is probably the simplest for the time been.

    enter image description here

    I apologise, this is the entire exposure to JavaFX I've had...

    public class TestJavaFXLoader extends JApplet {
    
        public static void main(String[] args) {
            new TestJavaFXLoader();
        }
    
        public TestJavaFXLoader() throws HeadlessException {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    Loader loader = new Loader();
                    loader.addPropertyChangeListener(new PropertyChangeListener() {
                        @Override
                        public void propertyChange(PropertyChangeEvent evt) {
                            if (evt.getPropertyName().equals("state") && evt.getNewValue().equals(SwingWorker.StateValue.DONE)) {
                                System.out.println("Load main app here :D");
                            }
                        }
                    });
                    loader.load();
                }
            });
        }
    
        public class Loader extends SwingWorker<Object, String> {
    
            private JWindow splash;
            private JLabel subMessage;
    
            public Loader() {
            }
    
            protected void loadSplashScreen() {
                try {
                    splash = new JWindow();
                    JLabel content = new JLabel(new ImageIcon(ImageIO.read(...))));
                    content.setLayout(new GridBagLayout());
                    splash.setContentPane(content);
    
                    GridBagConstraints gbc = new GridBagConstraints();
                    gbc.gridwidth = GridBagConstraints.REMAINDER;
    
                    subMessage = createLabel("");
    
                    splash.add(createLabel("Loading, please wait"), gbc);
                    splash.add(subMessage, gbc);
                    splash.pack();
                    splash.setLocationRelativeTo(null);
                    splash.setVisible(true);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
    
            protected JLabel createLabel(String msg) {
                JLabel message = new JLabel("Loading, please wait");
                message.setForeground(Color.CYAN);
                Font font = message.getFont();
                message.setFont(font.deriveFont(Font.BOLD, 24));
                return message;
            }
    
            public void load() {
                if (!EventQueue.isDispatchThread()) {
                    try {
                        SwingUtilities.invokeAndWait(new Runnable() {
                            @Override
                            public void run() {
                                loadSplashScreen();
                            }
                        });
                    } catch (Exception exp) {
                        exp.printStackTrace();
                    }
                } else {
                    loadSplashScreen();
                }
                execute();
            }
    
            @Override
            protected void done() {
                splash.dispose();
            }
    
            @Override
            protected void process(List<String> chunks) {
                subMessage.setText(chunks.get(chunks.size() - 1));
            }
    
            @Override
            protected Object doInBackground() throws Exception {
    
                publish("Preparing to load application");
                try {
                    Thread.sleep(2500);
                } catch (InterruptedException interruptedException) {
                }
                publish("Loading JavaFX...");
    
                runAndWait(new Runnable() {
                    @Override
                    public void run() {
                        new JFXPanel();
                    }
                });
    
                try {
                    Thread.sleep(2500);
                } catch (InterruptedException interruptedException) {
                }
                return null;
            }
    
            public void runAndWait(final Runnable run)
                    throws InterruptedException, ExecutionException {
                if (Platform.isFxApplicationThread()) {
                    try {
                        run.run();
                    } catch (Exception e) {
                        throw new ExecutionException(e);
                    }
                } else {
                    final Lock lock = new ReentrantLock();
                    final Condition condition = lock.newCondition();
                    lock.lock();
                    try {
                        Platform.runLater(new Runnable() {
                            @Override
                            public void run() {
                                lock.lock();
                                try {
                                    run.run();
                                } catch (Throwable e) {
                                    e.printStackTrace();
                                } finally {
                                    try {
                                        condition.signal();
                                    } finally {
                                        lock.unlock();
                                    }
                                }
                            }
                        });
                        condition.await();
    //                    if (throwableWrapper.t != null) {
    //                        throw new ExecutionException(throwableWrapper.t);
    //                    }
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }
    }
    

    I found the runAndWait code here