Java Tip 104: Make a splash with Swing

Create thread-safe splash screens using Swing

Almost all modern applications have a splash screen. Using a splash screen is a way to advertise your product. It is also used to indicate to the user that something is happening in your application during long startup times. Current literature explains how to create a splash screen but does not show how to integrate one into your application (see books by David Geary). Users can quickly remove some splash screens by simply clicking anywhere on them. Some splash screens stay visible only until the application is loaded. Other splash screens are visible even after the user has started the application.

Wouldn't you like to be able to do all those things in Java? By using Swing with threads, you can!

Here is a first swing (no pun intended) at a class for a splash screen created for use in an application:

class SplashWindow1 extends JWindow
{
    public SplashWindow1(String filename, Frame f)
    {
        super(f);
        JLabel l = new JLabel(new ImageIcon(filename));
        getContentPane().add(l, BorderLayout.CENTER);
        pack();
        Dimension screenSize =
          Toolkit.getDefaultToolkit().getScreenSize();
        Dimension labelSize = l.getPreferredSize();
        setLocation(screenSize.width/2 - (labelSize.width/2),
                    screenSize.height/2 - (labelSize.height/2));
        setVisible(true);
        screenSize = null;
        labelSize = null;
    }
}

The SplashWindow1 class extends Swing's JWindow. JWindow is a heavyweight container. It also has none of the normal items that appear in other windows, such as a title bar, window management buttons, or even a visible frame edge. Therefore, JWindow is perfect for a splash screen. The code above assumes an image file is located in the current directory. Once the image is loaded by way of the ImageIcon, the image is placed in the center of the JWindow. The JWindow is packed to let Swing resize the window correctly, and then it is moved to the center of the screen and set visible. You can find a similar version of that code in the reference material in Resources.

If you were to actually run the above code, you would unfortunately have a nicely centered splash screen that will not close! To make it close, you must add code:

class SplashWindow2 extends JWindow
{
    public SplashWindow2(String filename, Frame f)
    {
        super(f);
        JLabel l = new JLabel(new ImageIcon(filename));
        getContentPane().add(l, BorderLayout.CENTER);
        pack();
        Dimension screenSize =
          Toolkit.getDefaultToolkit().getScreenSize();
        Dimension labelSize = l.getPreferredSize();
        setLocation(screenSize.width/2 - (labelSize.width/2),
                    screenSize.height/2 - (labelSize.height/2));
        addMouseListener(new MouseAdapter()
            {
                public void mousePressed(MouseEvent e)
                {
                    setVisible(false);
                    dispose();
                }
            });
        setVisible(true);
    }
}

The only difference in that version of the SplashWindow class is that there is now an anonymous MouseListener installed on the JWindow. That will allow the user to click on the splash screen to make it disappear.

At that point, you will have a nice splash screen that can be removed but will not disappear on its own. You will then have to add code to remove the splash screen after a certain amount of time. Then you should be thinking threads. And if you've worked with Swing at all, you know that making threaded calls can be tricky at best. For reasons why and a more in-depth explanation of threads and Swing, see Resources.

class SplashWindow3 extends JWindow
{
    public SplashWindow3(String filename, Frame f, int waitTime)
    {
        super(f);
        JLabel l = new JLabel(new ImageIcon(filename));
        getContentPane().add(l, BorderLayout.CENTER);
        pack();
        Dimension screenSize =
          Toolkit.getDefaultToolkit().getScreenSize();
        Dimension labelSize = l.getPreferredSize();
        setLocation(screenSize.width/2 - (labelSize.width/2),
                    screenSize.height/2 - (labelSize.height/2));
        addMouseListener(new MouseAdapter()
            {
                public void mousePressed(MouseEvent e)
                {
                    setVisible(false);
                    dispose();
                }
            });
        final int pause = waitTime;
        final Runnable closerRunner = new Runnable()
            {
                public void run()
                {
                    setVisible(false);
                    dispose();
                }
            };
        Runnable waitRunner = new Runnable()
            {
                public void run()
                {
                    try
                        {
                            Thread.sleep(pause);
                            SwingUtilities.invokeAndWait(closerRunner);
                        }
                    catch(Exception e)
                        {
                            e.printStackTrace();
                            // can catch InvocationTargetException
                            // can catch InterruptedException
                        }
                }
            };
        setVisible(true);
        Thread splashThread = new Thread(waitRunner, "SplashThread");
        splashThread.start();
    }
}

The general idea here is to first create a Thread object that will pause for a specific amount of time. In the above code, the thread will pause for four seconds. When that thread wakes up, it will close the splash screen. Since Swing is not thread-safe, you should not affect the state of any UI component unless the code is being executed on the event-dispatching thread. The event-dispatching thread is the thread that handles drawing and event handling in Swing.

To get around that limitation, Swing designers gave the programmer the ability to add runnable objects to the UI event queue in a safe manner. In this case, you are going to use the runnable object's closeRunner to do the dirty work. You pass the runnable object to the static method SwingUtilities.invokeAndWait(). Then SwingUtilities.invokeAndWait() will execute all pending UI activity and execute the run method on the runnable object closeRunner, which is passed to the method. By using a separate thread to handle the splash screen's closing, the application that is displayed behind the splash screen is visible and responsive during the entire operation.

If you want a splash screen that is always visible and that the user cannot remove, you must remove the code that hides the splash screen. If you want a splash screen that the user must close manually, you can call the setVisible(false) and dispose() methods on the SplashWindow3 object just like any other JWindow.

Conclusion

By using the SwingUtilities.invokeAndWait() method, you can safely create a multithreaded Swing splash screen. A user can click on the splash screen to remove it, or the splash screen will disappear on its own after a set amount of time. The threading model that Swing supports will allow the application to remain responsive and usable behind the splash screen.

Tony Colston has been programming professionally since 1991, beginning with the development of ATMs and debit cards. He now works for Tennessee-based Buckman Labs, where he spends his days dreaming up new ways to distribute reports in realtime over the Web. His hobbies include playing basketball (badly), Quake III, and Diablo II. When he is not being a nerd, he spends his time worshiping his wife Beth who strangely thinks nerds are cool. You can check out his Webpage at http://members.xoom.com/Tonetheman.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more