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.