HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 
Working with Swing, by Paul Andrews

One of the strengths of Java is the abundance of standard APIs for doing everything from enterprise-level data access to manipulating data structures, sending and receiving e-mail and building GUIs. This broad sweep of APIs makes choosing how to implement the various parts of an application much simpler, but it also presents a problem: How do you best use the API?

Javadocs – the raw API documentation – are never enough for this purpose because they concentrate on each class or interface as a separate entity from the other classes. API specifications aren't much better because they deal with the minutiae of the API and with being a correct specification of how everything works. The most useful tool I've found for any newcomer to an API is the Java Tutorials (www.java.sun.com/docs/books/tutorial/index.html). These Tutorials guide you to correct and efficient use of an API rather than swamping you with detail.

This article extends the Tutorials and addresses some of the issues a developer might come across when writing an application that uses the Swing API. In it I will dive below the surface of the Swing and AWT toolkits to build a simple but useful component called a splash screen. I'm barely scratching the surface here, but you should learn some useful techniques and have a component that's useful in its own right.

What Is a Splash Screen?
A splash screen is the small window that appears while a program is loading. It usually has none of the decorations normally associated with a window, appears centrally positioned on the screen, and contains a graphic announcing the application and company and often some kind of progress indicator. Figure 1 provides an example.

Figure 1
Figure 1

My primary aim here is to produce a component that encapsulates most of the detail required to create such a window so it's easy for other people to include its functionality in their own applications. Essentially, it'll be an extension to the Swing Library.

Getting Started
First I need to select a component that will produce a top-level window with no decorations. A quick trawl through the API documentation reveals that JWindow is the class I should be using. Creating one of these is simple enough, but I'm going to add specialized functionality later so I may as well subclass JWindow so I can encapsulate that functionality in one place:

import javax.swing.JWindow;
public class SplashScreen extends JWindow {
...
}
Next I want to center the window on the screen. This involves a brief trip back into the AWT class libraries to use the Toolkit class. Ordinarily I wouldn't use this class directly, but it's the source of some useful information – in this case the screen dimensions. The Toolkit class itself is abstract, but can be used to locate an actual implementation. Once I have the implementation I can use it and information about the window size to position the window properly. To get the window size before it's visible, I just have to "pack" it. This code has to be executed after the window has been fully populated; the SplashScreen constructor would be a good place:
pack();
Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
setLocation((screenDim.width – getSize().width) / 2,
(screenDim.height – getSize().height) / 2);

Creating and showing my window now is simple. In the following example I pass in the application's main window as a parent:

SplashScreen ss = new SplashScreen(this);
ss.setVisible(true);
Once I'm finished with the window I'll need to remove it from the screen. I could do this with setVisible(false), but this will leave the window consuming resources (e.g., memory). As I'll never be showing it again, I may as well destroy it so that those resources are released. Merely removing my references to the window isn't sufficient as Swing has internal references that will prevent it from being garbage-collected. To remove Swing's references I need to call dispose() on the window, so I'll add a remove() function to my class that does this for me. This function will be made to perform other tasks later:
public void remove() {
dispose();
}
Filling in the Window
So far, I have a window with no contents, so I need to decide what contents to add and how to lay them out. Earlier I suggested that a splash screen should contain a graphic and a progress indicator. I'd also like to add a title as a separate component and lay out these components from top to bottom in the window. All products that my company ships will use the same layout for their splash screens, providing a consistent look across the whole product range, so I'm free to enforce the policy by encapsulating the code that produces it inside my class. If I wanted a more flexible splash screen, I could expose the standard JWindow functions to allow users to specify a layout manager and to add components as they want. To prevent my applications having to implement boilerplate code to create the splash screen, I'll implement as much as possible inside my class. For this reason my constructor will accept two JComponents for the title and progress indicator (more on why later) and the name of a graphic file to use as the main graphic:
public SplashScreen (JFrame parent,
String graphicName, JComponent title,
JComponent progress) {...}

Graphics is one area where the Java GUI libraries provide excellent facilities. My graphic file can be a JPEG, a GIF or even an animated GIF. All I have to do is create an ImageIcon, passing it the URL of a file containing the image:

ImageIcon i = new ImageIcon(url);
Resources
One issue: What location does this URL point to? I need a location that I can guarantee will exist wherever my application is installed. One of the best locations to fulfill this requirement is somewhere on the classpath. Even better would be to put the graphic in the same place as the rest of the classes that make up this specific application because then I can ship everything the application needs in one simple package.

This tells me where I should put the graphic, but I won't know where my application package will be installed. How can I discover what URL to use to actually load the graphic? Fortunately, the class loader will do this for me. Classloaders can be used to load anything off the classpath – not just classes. First I need to get a classloader – the one that was used to load me. This is easy enough to do. Every object has a method to get its class and hence its classloader. Then I use that classloader to obtain the URL of my graphic:

public URL getURL(String path) {
ClassLoader cl =
getClass().getClassLoader();
return cl.getResource(path);
}
Now I pass that URL to the constructor of ImageIcon as in the previous example.

This is a generally useful facility, so in the interests of reuse I should really encapsulate all this code in a class. ImageIcon has a constructor that takes a string – which it assumes is a filename. I could simply subclass ImageIcon and make the same constructor look for a resource of that name. If that fails, it can default to assuming the string is a filename. If I implement the other ImageIcon constructors too, I can use my new class wherever ImageIcon has been used already without breaking that code – plus I get the benefit of being able to distribute my application, along with the resources it needs, as one JAR file.

Now adding the resulting image to my display is simple. I just create a JLabel to hold it, then add the JLabel to my splash screen. I'll do this in my constructor so the code will look like this:

i = new ResourceImageIcon(path);
image = new JLabel(i);
add(image);
Careful analysis of running code shows I have a problem. The resources used by the instance of ImageIcon aren't completely released when I dispose of the window because ImageIcon caches the image data so that subsequent requests for the same image are faster. Worse, if the image is an animated GIF, the thread that performs the animation continues to run. To prevent this, I need to call flush() on the actual image. I may as well add this code to the remove function, which now becomes:
public void remove() {
i.getImage().flush();
dispose();
}

Swing and Threads
What about that progress indicator? I need to update the contents of one component, and this is one reason I passed in the components themselves rather than just, say, strings. If I make the progress indicator a JLabel, I can hold a reference to it in the object that created the SplashScreen instance and update its contents by calling setText(). I could use any other JComponent that I can dynamically change the contents of, but this will do for my purposes.

The only problem is that the Swing library isn't thread-safe once a component has been realized. In other words, I can't call setText() from any thread other than the Swing thread once I've made the splash screen visible. This would normally be okay because I'd be updating the display in response to a Swing event that would automatically ensure I was in the Swing thread. However, it's likely that I'll update my progress indicator in response to external events (connecting to servers, reading data, etc.), so how do I ensure that I call setText() from the right thread? Fortunately, Swing provides two functions that allow me to queue up work for it: SwingUtilities.invokeLater() and SwingUtilities.invokeAndWait(). The functions take a Runnable as an argument that's responsible for actually performing the work. The Runnable needs to have access to any data it needs at the time it runs. In the following example I call invokeAndWait() from a member function of a class called MainWin. The MainWin object holds a reference to the JLabel I'm using to display the message and to the text the Runnable will need to change the contents of the JLabel. The JLabel reference is called progress and the message reference is called status.

SwingUtilities.invokeAndWait (new Runnable() {
public void run() {
progress.setText(MainWin.this.status);
}
});

I'll have to use this mechanism whenever I want to update the splash screen once it's been made visible. This includes the call to "remove," which will remove it from the screen. I could put this code inside any function of SplashScreen that I think might be called from outside the Swing event loop. This would allow users of SplashScreen to call its methods without having to think about whether they're inside or outside the event loop. If I did this, I'd have to be very careful that I didn't cause deadlocks and that the data I needed didn't change between the user calling my function and Swing invoking the Runnable.

Tidying Up
So far so good, but the window still looks a little messy. I'd like to center the title and the progress indicator and make sure that the colors coordinate properly with the graphic. The code to do this appears in Listing 1. I also think a border should be added to the original wish list for the appearance of the splash screen – I can do this using the BorderFactory class. Plus the image needs to be centered and the components need to be laid out in the right order, running from top to bottom: title, image and then the progress indicator. The code to do all this appears in Listing 2.

Keeping Busy
Finally, I'd like to display a watch cursor while the pointer is over the window and prevent the user from interacting with it. Adding such a cursor can be done simply by adding the following line of code to the constructor for the splash screen:

setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

The easiest way to prevent user interaction with the splash screen is to make the glass pane visible. This pane, which is transparent, is a standard part of Swing windows. It's used to intercept events that would normally go to the window itself – for the splash screen I only need to intercept mouse events. The following code in the constructor of the splash screen will do it:

getGlassPane().addMouseListener(new MouseAdapter() {});
getGlassPane().setVisible(true);
For windows that already have the keyboard focus, I'd also have to intercept key events, but as the splash screen will never receive the focus, this code is sufficient.

Summary
I'm now satisfied that I have a component that fulfills my criteria for a splash screen. This useful component can be used straight out of the box, but more important – for the purposes of this article – it explores some of the real-world problems that GUI developers encounter when writing a Swing application. In particular:

  • Using the Toolkit to obtain information about the screen
  • Using a classloader to locate resources
  • Ensuring that all components are freed up when they're no longer needed
  • Accessing Swing components from other threads
  • Fine-tuning the appearance of a window
  • Putting a window into a "busy" state
Author Bio
Paul Andrews has worked for over 20 years as a computer scientist in the IT industry and has been actively programming in Java since 1997. Paul specializes in the design of large distributed OO architectures for the implementation of secure e-commerce systems. He can be contacted at: [email protected]

	


Listing 1

public void createSplashScreen()
{
  // Create a JLabel for the title. Ensure that the text is   
  // centered within the JLabel and that the JLabel is cen-
  // tered within the window.
  JLabel _title = new JLabel("SplashScreen Test v3.1", JLa- 
                            bel.CENTER);
  _title.setAlignmentX(Component.CENTER_ALIGNMENT);

  // Set the color of the text in the title. The background    
  // will be that of the window itself.
  _title.setForeground(Color.green);

  // ditto for the progress indicator
  progressLabel = new JLabel("Connecting to server...", JLa-   
                            bel.CENTER);
  progressLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
  progressLabel.setForeground(Color.green);

  // Create the SplashScreen
  splashScreen = new SplashScreen(
    this, "net/jools/test/SplashTest.jpg", _title, progressLabel);

  // Set the background color of the SplashScreen
  splashScreen.getContentPane().setBackground(Color.white);

  // Show the SplashScreen
  splashScreen.setVisible(true);
}

Listing 2

public SplashScreen(...)
{
  JComponent pane = (JComponent)getContentPane();

  // Use a vertical BoxLayout to arrange the components
  // top to bottom.
  pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));

  i = new ResourceImageIcon(splashName);

  // To ensure that the image is centered horizontally, cen-
  // ter it in the JLabel and center the JLabel in the window.
  image = new JLabel(i, JLabel.CENTER);
  image.setAlignmentX(Component.CENTER_ALIGNMENT);

  // Add a border to the window
  pane.setBorder(BorderFactory.createCompoundBorder(
    BorderFactory.createRaisedBevelBorder(),
    BorderFactory.createLoweredBevelBorder()));

  // The order in which the components are now added to the
  // window is important. First the title, then the image,
  // then the footer.

  // To ensure that the title component spans the full width 
  // of the window set its maximum sizes. The caller is 
  // responsible for its alignment
  if (title != null) {
    title.setMaximumSize(
      new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
      pane.add(title);
  }

  pane.add(image);

  if (footer != null) {
    footer.setMaximumSize(
      new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
    pane.add(footer);
 }
  ...
}
  
 
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.