Using Swing Components

You've heard Swing mentioned here and there so far in this tutorial, but what exactly is it? Swing, in a nutshell, is a set of classes used to create user interface elements. It includes all sorts of common components such as windows, buttons, pop-up menus, drop-down lists, text-input boxes, check boxes, and labels. You'll use Swing components to implement your user interfaces. You've already seen Swing's JFrame used as the full-screen window in the ScreenManager class. Actually, you used a JFrame object instead of just a Window or Frame just so you can integrate with Swing. Because Swing renders its own components, you can draw Swing components in your own rendering loop. This is great news because it means that you can integrate all of Swing's functionality into your full-screen games. You don't have to reinvent the wheel by creating your own user interface architecture! Plus, Swing can be customized with your own look-and-feels, so you can create an individual look for your user interface. Swing has a pretty large API, and this is a game tutorial, not a Swing tutorial, so not everything about Swing is discussed here. If you're looking for the nitty-gritty details on Swing, plenty of tutorials and web tutorials are available on the subject. What we will talk about is getting Swing to work with your games and creating some basic user interface components. So let's get started!

Swing Basics

Creating most Swing components is straightforward. Most user interface elements are a class, so you just have to create an instance of that class. For example, to create an OK button, simply create a new JButton object:

JButton okButton = new JButton("OK");


This creates a JButton with all the default parameters and with a text label of OK. Swing components start with J, as in JLabel, JTextField, and so on, and they're all derived from the JComponent class. Next, you need to actually add it to the screen so it can be clicked on. The components are added to a top-level container, which, in this case, is the JFrame. Components are added to the JFrame's content pane. When a component is added to the content pane, Swing handles all the mouse clicks for it, so you don't have to. Here's an example of adding a button to the content pane:

JFrame frame = screen.getFullScreenWindow();
frame.getContentPane().add(okButton);


This adds the button to the content pane, but by default the button is not positioned anywhere and will probably just end up in the upper-left corner of the screen. You can do two things to fix this. The first is to lay out all components explicitly by calling the setLocation() method:

okButton.setLocation(200, 100);


Explicit layout is easy to implement, but it's not perfect. Imagine you lay out two buttons next to each other. On your system, it could look fine, but on a different system, the default Swing look and feel could create larger buttons that end up overlapping each other or otherwise just look unattractive. The second way to lay out components is to use a LayoutManager. LayoutManagers take each component's size into consideration and position the components according to certain rules. Several different LayoutManagers exist, and each positions components in different ways. FlowLayout, for example, lays out components on the screen in a left-to-right, top-to-bottom fashion. To use a LayoutManager, call the setLayout() method of the content pane. Here's an example of using a FlowLayout:

frame.getContentPane().setLayout(new FlowLayout());


This makes sure all components added to the content pane are repositioned according to FlowLayout's rules.

Tricking Swing to Work in Full-Screen Mode

As mentioned before, all components are added to JFrame's content pane. The content pane is actually a component in another container—namely, JFrame's layered pane. The layered pane contains many different "layers" of components so that certain components can appear on top of others. For example, Tooltips and pop-up menus should appear above components in the content pane, so they are positioned above the content pane in the layered pane. To draw all the Swing components, just draw what's in the layered pane by calling the paintComponents() method. This is done in the animation loop:

// draw our graphics draw(g);
// draw Swing components JFrame frame = screen.getFullScreenWindow();
frame.getLayeredPane().paintComponents(g);


Two problems arise here. The first problem is that the content pane actually draws its background, which hides everything underneath it. You want your Swing components to appear above your own graphics. This problem is easy to fix—you just need to make the content pane transparent:

if (contentPane instanceof JComponent) {
 ((JComponent)contentPane).setOpaque(false);
}


This code makes sure the content pane draws only its components, not its background. The second problem has to do with how Swing renders its components. In a normal Swing app, you don't have to call paintComponents()—Swing does all its rendering automatically in the AWT event dispatch thread. However, you haven't turned off this functionality. Whenever a component changes its appearance, such as when you hold down a button and it appears to be pressed, the component requests that it be repainted by calling the repaint() method. Obviously, you don't want any rendering to occur in the AWT event dispatch thread because this can conflict with your own rendering and cause flicker or other conflicts. In your code, you could potentially ignore the repaint request because if a button is pressed, its appearance will change in the next draw in the animation loop. To fix this, how do you capture the repaint requests and ignore them? Well, all repaint requests go to a RepaintManager, which basically manages the requests and forwards the requests to actually paint the components. You can simply override this functionality by creating a NullRepaintManager, shown in Listing 3.9. NullRepaintManager simply causes repaint requests to disappear.

Listing 3.9 NullRepaintManager.java
package com.brackeen.javagamebook.graphics;
import javax.swing.RepaintManager;
import javax.swing.JComponent;
/**
 The NullRepaintManager is a RepaintManager that doesn't
 do any repainting. Useful when all the rendering is done
 manually by the app.
*/
public class NullRepaintManager extends RepaintManager {
 /**
 Installs the NullRepaintManager.
 */
 public static void install() {
 RepaintManager repaintManager = new NullRepaintManager();
 repaintManager.setDoubleBufferingEnabled(false);
 RepaintManager.setCurrentManager(repaintManager);
 }
 public void addInvalidComponent(JComponent c) {
 // do nothing
 }
 public void addDirtyRegion(JComponent c, int x, int y,
 int w, int h)
 {
 // do nothing
 }
 public void markCompletelyDirty(JComponent c) {
 // do nothing
 }
 public void paintDirtyRegions() {
 // do nothing
 }
}


Easy enough right? NullRepaintManager extends the default RepaintManager class and overrides key methods to simply do nothing. That way, repaint events aren't sent to the AWT, and you won't see flickering components. That's it! To install the NullRepaintManager, just call NullRepaintManager.install(). NOTE Keep in mind that Swing components aren't thread-safe. When a Swing component is visible, you don't want to modify its state at the same time the AWT event dispatch thread modifies its state. If you need to modify a Swing component after it is visible, do so in the event dispatch thread, like this:

EventQueue.invokeAndWait(new Runnable() {
 public void run() {
 doSomething();
 }
});


This invokes the code in the Runnable in the AWT event dispatch thread and waits for it to finish. Alternatively, you can use invokeLater() if you don't need to wait for it to finish.


Screenshot


   
Comments