CONTENTS |
Swing is Java's user interface toolkit. It was developed during the life of Java 1.1 and became part of the core APIs with 1.2. Swing provides classes representing interface items such as windows, buttons, combo boxes, trees, tables, and menus—everything you need to build a user interface for your Java application. The javax.swing package (and its numerous subpackages) contain the Swing user interface classes.[1]
Swing is part of a larger collection of software called the Java Foundation Classes (JFC). JFC includes the following APIs:
The Abstract Window Toolkit (AWT), the original user interface toolkit
Swing, the pure Java user interface toolkit
Accessibility, which provides tools for integrating nonstandard input and output devices into your user interfaces
The 2D API, a comprehensive set of classes for high-quality drawing
Drag and Drop, an API that supports the drag-and-drop metaphor
JFC is the largest and most complicated part of the standard Java platform, so it shouldn't be any surprise that we'll take several chapters to discuss it. In fact, we won't even get to talk about all of it, just the most important parts—Swing and the 2D API. Here's the lay of the land:
This chapter covers the basic concepts you need to understand how to build user interfaces with Swing.
Chapter 16 discusses the basic components from which user interfaces are built: buttons, lists, text fields, checkboxes, and so on.
Chapter 17 dives further into the Swing toolkit, describing text components, trees, tables, and other neat stuff.
Chapter 18 discusses layout managers, which are responsible for arranging components within a window.
Chapter 19 discusses the fundamentals of drawing, including simple image displays.
Chapter 20 covers the image generation and processing tools that are in the java.awt.image package. We'll throw in audio and video for good measure.
We can't cover the full functionality of Swing in this book; if you want the whole story, see Java Swing by Marc Loy, Robert Eckstein, Dave Wood, Brian Cole, and James Elliott (O'Reilly). Instead, we'll cover the basic tools you are most likely to use and show some examples of what can be done with some of the more advanced features. Figure 15-1 shows the user interface component classes of the javax.swing package.
To understand Swing, it helps to understand its predecessor, the Abstract Window Toolkit (AWT). As its name suggests, AWT is an abstraction. Like the rest of Java it was designed to be portable; its functionality is the same for all Java implementations. However, people generally expect their applications to have a consistent look and feel and that is usually different on different platforms. So AWT was designed to work in the same way on all platforms, yet have the appearance of a native application. You could choose to write your code under Windows, then run it on an X Window System or a Macintosh. To achieve platform independence, AWT uses interchangeable toolkits that interact with the host windowing system to display user interface components. This shields your application from the details of the environment it's running in. Let's say you ask AWT to create a button. When your application or applet runs, a toolkit appropriate to the host environment renders the button appropriately: on Windows, you can get a button that looks like other Windows buttons; on a Macintosh, you can get a Mac button; and so on.
AWT had some serious shortcomings. The worst was that the use of platform-specific toolkits meant that AWT applications might be subtly incompatible on different platforms. Furthermore, AWT lacked advanced user interface components, like trees and grids, that were not common to all environments.
Swing takes a fundamentally different approach. Instead of using native toolkits to supply interface items such as buttons and combo boxes, components in Swing are implemented in Java itself. This means that, whatever platform you're using, by default a Swing button (for example) looks the same. However, Swing also provides a powerful, pluggable look-and-feel API that allows native operating system appearance to be substituted at the Java level. Working purely in Java makes Swing much less prone to platform-specific bugs, which were a problem for AWT. It also means that Swing components are much more flexible and can be extended and modified in your applications.
Working with user interface components in Swing is meant to be easy. When building a user interface for your application, you'll be working with prefabricated components. It's easy to assemble a collection of user interface components (buttons, text areas, etc.) and arrange them inside containers to build complex layouts. You can also use simple components as building blocks for making entirely new kinds of interface gadgets that are completely portable and reusable.
Swing uses layout managers to arrange components inside containers and control their sizing and positioning. Layout managers define a strategy for arranging components instead of specifying absolute positions. For example, you can define a user interface with a collection of buttons and text areas and be reasonably confident that it will always display correctly, even if the user resizes the application window. It doesn't matter what platform or user interface look-and-feel you're using; the layout manager should still position them sensibly with respect to each other.
The next two chapters contain examples using most of the components in the javax.swing package. But before we dive into those examples, we need to spend a bit of time talking about the concepts Swing uses for creating and handling user interfaces. This material should get you up to speed on GUI concepts and how they are used in Java.
A component is the fundamental user interface object in Java. Everything you see on the display in a Java application is a component. This includes things like windows, panels, buttons, checkboxes, scrollbars, lists, menus, and text fields. To be used, a component usually must be placed in a container. Container objects group components, arrange them for display using a layout manager, and associate them with a particular display device. All Swing components are derived from the abstract javax.swing.JComponent class, as you saw in Figure 15-1. For example, the JButton class is a subclass of AbstractButton, which is itself a subclass of the JComponent class.
JComponent is the root of the Swing component hierarchy, but it descends from the AWT Container class. At this bottom level, Swing is based on AWT, so our conversation occasionally delves into the AWT package. Container's superclass is Component, the root of all AWT components, and Component's superclass is, finally, Object. Because JComponent inherits from Container, it has the capabilities of both a component and a container.
AWT and Swing, then, have parallel hierarchies. The root of AWT's hierarchy is Component, while Swing's components are based on JComponent. You'll find similar classes in both hierarchies, such as Button and JButton, List and JList. But Swing is much more than a replacement for AWT—it contains sophisticated components as well as a real implementation of the Model-View-Controller (MVC) paradigm, which we'll discuss later.
For the sake of simplicity, we can split the functionality of the JComponent class into two categories: appearance and behavior. The JComponent class contains methods and variables that control an object's general appearance. This includes basic attributes such as its visibility, its current size and location, and certain common graphical defaults, such as font and color. The JComponent class also contains methods implemented by specific subclasses to produce graphical displays.
When a component is first displayed, it's associated with a particular display device. The JComponent class encapsulates access to its display area on that device. It includes tools for accessing graphics, for working with off-screen resources, and for receiving user input.
When we talk about a component's behavior, we mean the way it responds to user-driven events. When the user performs an action (such as pressing the mouse button) within a component's display area, a Swing thread delivers an event object that describes what happened. The event is delivered to objects that have registered themselves as listeners for that type of event from that component. For example, when the user clicks on a button, the button delivers an ActionEvent object. To receive those events, an object registers with the button as an ActionListener.
Events are delivered by invoking designated event-handler methods within the receiving object (the "listener"). A listener object prepares itself to receive events by implementing methods (e.g., actionPerformed()) for the types of events in which it is interested. Specific types of events cover different categories of component user interaction. For example, MouseEvents describe activities of the mouse within a component's area, KeyEvents describe key presses, and higher-level events (such as ActionEvents) indicate that a user interface component has done its job.
We will describe events thoroughly in this chapter because they are so fundamental to the way in which user interfaces function in Java. But they aren't limited to building user interfaces; they are an important interobject communications mechanism, which may be used by completely nongraphical parts of an application, as well. They are particularly important in the context of JavaBeans, which uses events as an extremely general notification mechanism.
Swing's event architecture is very flexible. Instead of requiring every component to listen for and handle events for its own bit of the user interface, an application may register event "handler" objects to receive the events for one or more components and "glue" those events to the correct application logic. A container might, for example, process the events relating to its child components.
One responsibility a container always has is laying out the components it contains. A component informs its container when it does something that might affect other components in the container, such as changing its size or visibility. The container then tells its layout manager that it is time to rearrange the child components.
As mentioned, Swing components are also containers. Containers can manage and arrange JComponent objects without knowing what they are or what they are doing. Components can be swapped and replaced with new versions easily and combined into composite user interface objects that can be, themselves, treated as individual components. This lends itself well to building larger, reusable user interface items.
Swing components are peerless, or lightweight. To understand these terms, you'll have to understand the peer system that AWT used (or still uses). Getting data out to a display medium and receiving events from input devices involve crossing the line from Java to the real world. The real world can be a nasty place full of architecture dependence, local peculiarities, and strange physical devices such as mice, trackballs, and `69 Buicks.
At some level, our components have to talk to objects that contain native methods to interact with the host operating environment. To keep this interaction as clean and well-defined as possible, AWT used a set of peer interfaces. The peer interface made it possible for a pure Java-language graphic component to use a corresponding real component—the peer object—in the native environment. You didn't generally deal directly with peer interfaces or the objects behind them; peer handling was encapsulated within the Component class.
AWT relied heavily on peers. For example, if you created a window and added eight buttons to it, AWT would create nine peers for you—one for the window and one for each of the buttons. As an application programmer, you wouldn't ever have to worry about the peers, but they would always be lurking under the surface, doing the real work of interacting with your operating system's windowing toolkit.
In Swing, by contrast, most components are peerless, or lightweight. This means that Swing components don't have any direct interaction with the underlying windowing system. They draw themselves in their parent container and respond to user events, all without the aid of a peer. All the components in Swing are written in pure Java, with no native code involved. In Swing, only the top-level (lowest API level) windows interact with the windowing system. These Swing containers descend from AWT counterparts, and thus still have peers. In Swing, if you create a window and add eight buttons to it, only one peer is created—for the window. Because it has fewer interactions with the underlying windowing system than AWT, Swing is more reliable.
With lightweight components, it is easy to change their appearance. Since each component draws itself, instead of relying on a peer, it can decide at runtime how to render itself. Accordingly, Swing supports different look-and-feel schemes, which can be changed at runtime. (A look-and-feel is the collected appearance of components in an application.) Look-and-feels based on Windows, Macintosh, and Motif are available (though licensing issues may encumber their use on various platforms), as well as an entirely original one called Metal or the "Java Look and Feel," which is the default scheme.
Before continuing our discussion of GUI concepts, we want to make a brief aside and talk about the Model-View-Controller (MVC) framework. MVC is a method of building reusable components that logically separates the structure, presentation, and behavior of a component into separate pieces. MVC is primarily concerned with building user interface components, but the basic ideas can be applied to many design issues; its principles can be seen throughout Java.
The fundamental idea behind MVC is the separation of the data model for an item from its presentation. For example, we can draw different representations (e.g., bar graphs, pie charts) of the data in a spreadsheet. The data is the model; the particular representation is the view. A single model can have many views that present the data differently. A user interface component's controller defines and governs its behavior. Typically, this includes changes to the model, which, in turn, cause the view(s) to change, also. For a checkbox component, the data model could be a single boolean variable, indicating whether it's checked or not. The behavior for handling mouse-click events would alter the model, and the view would examine that data when it draws the on-screen representation.
The way in which Swing objects communicate, by passing events from sources to listeners, is part of this MVC concept of separation. Event listeners are "observers" (controllers) and event sources are "observables" (models).[2] When an observable changes or performs a function, it notifies all its observers of the activity.
Swing components explicitly support MVC. Each component is actually composed of two pieces. One piece, called the UI-delegate, is responsible for the "view" and "controller" roles. It takes care of drawing the component and responding to user events. The second piece is the data model itself. This separation makes it possible for multiple Swing components to share a single data model. For example, a read-only text box and a drop-down list box could use the same list of strings as a data model.
In an event-driven environment such as Swing, components can be asked to draw themselves at any time. In a more procedural programming environment, you might expect a component to be involved in drawing only when first created or when it changes its appearance. In Java, components act in a way that is closely tied to the underlying behavior of the display environment. For example, when you obscure a component with another window and then reexpose it, a Swing thread may ask the component to redraw itself.
Swing asks a component to draw itself by calling its paint() method. paint() may be called at any time, but in practice, it's called when the object is first made visible, whenever it changes its appearance, or whenever some tragedy in the display system messes up its area. Because paint() can't make any assumptions about why it was called, it must redraw the component's entire display. The system may limit the drawing if only part of the component needs to be redrawn, but you don't have to worry about this.
A component never calls its paint() method directly. Instead, if a component requires redrawing, it schedules a call to paint() by invoking repaint(). The repaint() method asks Swing to schedule the component for repainting. At some point in the future, a call to paint() occurs. Swing is allowed to manage these requests in whatever way is most efficient. If there are too many requests to handle, or if there are multiple requests for the same component, the thread can reschedule a number of repaint requests into a single call to paint(). This means that you can't predict exactly when paint() is called in response to a repaint(); all you can expect is that it happens at least once, after you request it.
Calling repaint() is normally an implicit request to be updated as soon as possible. Another form of repaint() allows you to specify a time period within which you would like an update, giving the system more flexibility in scheduling the request. The system tries to repaint the component within the time you specify, but if you happen to make more than one repaint request within that time period, the system may simply condense them to carry out a single update within the time you specified. An application performing animation could use this method to govern its refresh rate (by specifying a period that is the inverse of the desired frame rate).
Swing components can act as containers, holding other components. Because every Swing component does its own drawing, Swing components are responsible for telling contained components to draw themselves. Fortunately, this is all taken care of for you by a component's default paint() method. If you override this method, however, you have to make sure to call the superclass's implementation like this:
public void paint(Graphics g) { super.paint(g); ... }
There's a cleaner way around this problem. All Swing components have a method called paintComponent(). While paint() is responsible for drawing the component as well as its contained components, paintComponent()'s sole responsibility is drawing the component itself. If you override paintComponent() instead of paint(), you won't have to worry about drawing contained components.
Both paint() and paintComponent() receive a single argument: a Graphics object. The Graphics object represents the component's graphics context. It corresponds to the area of the screen on which the component can draw and provides the methods for performing primitive drawing and image manipulation. (We'll look at the Graphics class in detail in Chapter 17.)
Standard Swing components can be turned on and off by calling the setEnabled() method. When a component such as a JButton or JTextField is disabled, it becomes "ghosted" or "greyed-out" and doesn't respond to user input.
For example, let's see how to create a component that can be used only once. This requires getting ahead of the story; we won't explain some aspects of this example until later. Earlier, we said that a JButton generates an ActionEvent when it is pressed. This event is delivered to the listeners' actionPerformed() method. The following code disables whatever component generated the event:
public boolean void actionPerformed(ActionEvent e ) { ((JComponent)e.getSource( )).setEnabled(false); }
This code calls getSource() to find out which component generated the event. We cast the result to JComponent because we don't necessarily know what kind of component we're dealing with; it might not be a button, because other kinds of components can generate action events. Once we know which component generated the event, we disable it.
You can also disable an entire container. Disabling a JPanel, for instance, disables all the components it contains.
In order to receive keyboard events, a component has to have keyboard focus. The component with the focus is the currently selected component on the screen and is usually highlighted visually. It receives all keyboard event information until the focus changes to a new component. Typically a component receives focus when the user clicks on it with the mouse or navigates to it using the keyboard. A component can ask for focus with the JComponent`s requestFocus() method. You can configure whether a given component is eligible to receive focus with the setFocusable() method. By default most components, including things such as buttons and checkboxes, are "focusable." To make an entire window and its components nonfocusable, use the Window setFocusableWindowState() method.
The control of focus is at the heart of the user's experience with an application. Especially with text entry fields and forms, users are accustomed to a smooth transfer of focus with the use of keyboard navigation cues (e.g., Tab and Shift-Tab for forward and backward field navigation). The management of focus in a large GUI with many components could be complex. Fortunately, as of Java 1.4, Swing handles almost all this behavior for you, so in general you don't have to implement code to specify how focus is transferred. The 1.4 release introduced an entirely new focus subsystem. The flexible KeyboardFocusManager API provides the expected common behavior by default and allows customization via FocusTraversalPolicy objects. We'll discuss focus-related events later in this chapter and focus navigation more in Chapter 17.
The JComponentclass is very large; it has to provide the base-level functionality for all the various kinds of Java GUI objects. It inherits a lot of functionality from its parent Container and Component classes. We don't have room to document every method of the JComponent class here, but we'll flesh out our discussion by covering some more of the important ones:
Return the container that holds this component.
Get or assign the String name of this component. Naming a component is useful for debugging. The name is returned by toString().
Make the component visible or invisible, within its container. If you change the component's visibility, the container's layout manager automatically lays out its visible components.
Get and set the foreground and background colors for this component. The foreground color of any component is the default color used for drawing. For example, it is the color used for text in a text field as well as the default drawing color for the Graphics object passed to the component's paint() and paintComponent() methods. The background color is used to fill the component's area when it is cleared by the default implementation of update().
Get and set the current size of the component. Note that a layout manager may change the size of a component even after you've set its size yourself. To change the size a component "wants" to be, use setPreferredSize(). There are other methods in JComponent to set its location, but normally this is the job of a layout manager.
Use these methods to examine or sets the preferred size of a component. Layout managers attempt to set components to their preferred sizes. If you change a component's preferred size, you must call the method revalidate() on the component to get it laid out again.
Get or set the type of cursor (mouse pointer) used when the mouse is over this component's area. For example:
JComponent myComponent = ...; Cursor crossHairs = Cursor.getPredefinedCursor( Cursor.CROSSHAIR_CURSOR ); myComponent.setCursor( crossHairs );
A container is a kind of component that holds and manages other components. JComponent objects can be containers because the JComponent class descends from the Container class. However, you wouldn't normally add components directly to specialized components such as buttons or lists.
Three of the most useful general container types are JFrame, JPanel, and JApplet. A JFrame is a top-level window on your display. JFrame is derived from JWindow, which is pretty much the same but lacks a border. A JPanel is a generic container element that groups components inside JFrames and other JPanels. The JApplet class is a kind of container that provides the foundation for applets that run inside web browsers. Like other JComponents, a JApplet can contain other user-interface components. You can also use the JComponent class directly, like a JPanel, to hold components inside another container. With the exception of JFrame and JWindow, all the components and containers in Swing are lightweight.
A container maintains the list of "child" components it manages and has methods for dealing with those components. Note that this child relationship refers to a visual hierarchy, not a subclass/superclass hierarchy. By themselves, most components aren't very useful until they are added to a container and displayed. The add() method of the Container class adds a component to the container. Thereafter, this component can be displayed in the container's display area and positioned by its layout manager. You can remove a component from a container with the remove() method.
A layout manager is an object that controls the placement and sizing of components within the display area of a container. A layout manager is like a window manager in a display system; it controls where the components go and how big they are. Every container has a default layout manager, but you can install a new one by calling the container's setLayout() method.
Swing comes with a few layout managers that implement common layout schemes. The default layout manager for a JPanel is a FlowLayout, which tries to place objects at their preferred size from left to right and top to bottom in the container. The default for a JFrame is a BorderLayout, which places objects at specific locations within the window, such as NORTH, SOUTH, and CENTER. Another layout manager, GridLayout, arranges components in a rectangular grid. The most general (and difficult to use) layout manager is GridBagLayout, which lets you do the kinds of things you can do with HTML tables. (We'll get into the details of all these layout managers in Chapter 16.)
When you add a component to a container using a simple layout manager, you'll often use the version of add() that takes a single Component as an argument. However, if you're using a layout manager that uses "constraints," such as BorderLayout or GridBagLayout, you must specify additional information about where to put the new component. For that you can use the version that takes a constraint object. Here's how to place a component at the top edge of a container that uses a BorderLayout manager:
myContainer.add(myComponent, BorderLayout.NORTH);
In this case, the constraint object is the static member variable NORTH. GridBagLayout uses a much more complex constraint object to specify positioning.
Insets specify a container's margins; the space specified by the container's insets won't be used by a layout manager. Insets are described by an Insets object, which has four public int fields: top, bottom, left, and right. You normally don't need to worry about the insets; the container sets them automatically, taking into account extras like the menu bar that may appear at the top of a frame. To find the insets, call the component's getInsets() method, which returns an Insets object.
With the standard layout managers, components are not allowed to overlap. However, if you use custom-built layout managers or absolute positioning, components within a container may overlap. If they do, the order in which components were added to a container matters. When components overlap, they are "stacked" in the order in which they were added: the first component added to the container is on top, and the last is on the bottom. To give you more control over stacking, two additional forms of the add() method take an additional integer argument that lets you specify the component's exact position in the container's stacking order.
A layout manager arranges the components in a container only when asked to. Several things can mess up a container after it's initially laid out:
Changing its size
Resizing or moving one of its child components
Adding, showing, removing, or hiding a child component
Any of these actions cause the container to be marked invalid. This means that it needs to have its child components readjusted by its layout manager. In most cases, Swing readjusts the layout automatically. All components, not just containers, maintain a notion of when they are valid or invalid. If the size, location, or internal layout of a Swing component changes, its revalidate() method is automatically called. Internally, the revalidate() method first calls the method invalidate() to mark the component and all its enclosing containers as invalid. It then validates the tree. Validation descends the hierarchy, starting at the nearest validation root container, recursively validating each child. Validating a child Container means invoking its doLayout() method, which asks the layout manager to do its job and then notes that the Container has been reorganized by setting its state to valid again. A validation root is a container that can accommodate children of any size such as JScrollPane.
There are a few cases in which you may need to tell Swing to fix things manually. One example is when you change the preferred size of a component (as opposed to its actual onscreen size). To clean up the layout, call the revalidate() method. So, for example, if you have a small JPanel—say a keypad holding some buttons—and you change the preferred size of the JPanel by calling its setPreferredSize() method, you should also call revalidate() on the panel or its immediate container. The layout manager of the panel then rearranges its buttons to fit inside its new area.
There are a few additional tools of the Container class we should mention:
Returns the container's components in an array.
Generates a list of the components in this container and writes them to the specified PrintWriter.
Tells you what component is at the specified coordinates in the container's coordinate system.
You can use the ContainerListener interface to automate setting up a container's new components. A container that implements this interface can receive an event whenever it gains or loses a component. This facility makes it easy for a container to micro-manage its components.
Windows and frames are the top-level containers for Java components. A JWindow is simply a plain, graphical screen that displays in your windowing system. Windows have no frills; they are mainly suitable for making "splash" screens and pop-up windows. JFrame, on the other hand, is a subclass of JWindow that has a border and can hold a menu bar. You can drag a frame around on the screen and resize it, using the ordinary controls for your windowing environment. Figure 15-2 shows a JFrame on the left and a JWindow on the right.
All other Swing components and containers must be held, at some level, inside a JWindow or JFrame. Applets are a kind of Container. Even applets must be housed in a frame or window, though normally you don't see an applet's parent frame because it is part of (or simply is) the browser or appletviewer displaying the applet.
JFrames and JWindows are the only components that can be displayed without being added or attached to another Container. After creating a JFrame or JWindow, you can call the setVisible() method to display it. The following short application creates a JFrame and a JWindow and displays them side by side, just like in Figure 15-2.
//file: TopLevelWindows.java import javax.swing.*; public class TopLevelWindows { public static void main(String[] args) { JFrame frame = new JFrame("The Frame"); frame.setSize(300, 300); frame.setLocation(100, 100); JWindow window = new JWindow( ); window.setSize(300, 300); window.setLocation(500, 100); frame.setVisible(true); window.setVisible(true); } }
The JFrame constructor can take a String argument that supplies a title, displayed in the JFrame's titlebar. (Or you can create the JFrame with no title and call setTitle() to supply the title later.) The JFrame's size and location on your desktop is determined by the calls to setSize() and setLocation(). After creating the JFrame, we create a JWindow in almost exactly the same way. The JWindow doesn't have a titlebar, so there are no arguments to the JWindow constructor.
Once the JFrame and JWindow are set up, we call setVisible(true) to get them on the screen. The setVisible() method returns immediately, without blocking. Fortunately, our application does not exit, even though we've reached the end of the main() method, because the windows are still visible. You can close the JFrame by clicking on the close button in the titlebar. JFrame's default behavior is to hide itself when you click on the box by calling setVisible(false). You can alter this behavior by calling the setDefaultCloseOperation() method or by adding an event listener, which we'll cover later. Since we haven't arranged any other means here, you will have to hit Ctrl-C or whatever keystroke kills a process on your machine to stop execution of the TopLevelWindows application.
The setLocation() method of the Component class can be used on a JFrame or JWindow to set its position on the screen. The x and y coordinates are relative to the screen's origin (the top left corner).
You can use the toFront() and toBack() methods to place a JFrame or JWindow in front of, or behind, other windows. By default, a user is allowed to resize a JFrame, but you can prevent resizing by calling setResizable(false) before showing the JFrame.
On most systems, frames can be "iconified"; that is, they can be shrunk down and represented by a little icon image. You can get and set a frame's icon image by calling getIconImage() and setIconImage(). As you can with all components, you can set the cursor by calling the setCursor() method.
Windows and frames don't behave exactly like regular containers. With other containers, you can add child components with the add() method. JFrame and JWindow have some extra stuff in them (mostly to support Swing's peerless components), so you can't just add() components directly. Instead, you need to add the components to the associated content pane. The content pane is just a Container that covers the visible area of the JFrame or JWindow. Whenever you create a new JFrame or JWindow, a content pane is automatically created for you. You can retrieve it with getContentPane(). Here's another example that creates a JFrame and adds some components to its content pane:
//file: MangoMango1.java import java.awt.*; import javax.swing.*; public class MangoMango1 { public static void main(String[] args) { JFrame frame = new JFrame("The Frame"); frame.setLocation(100, 100); Container content = frame.getContentPane( ); content.setLayout(new FlowLayout( )); content.add(new JLabel("Mango")); content.add(new JButton("Mango")); frame.pack( ); frame.setVisible(true); } }
The call to JFrame's pack() method tells the frame window to resize itself to the minimum size required to hold all its components. Instead of having to determine the size of the JFrame, pack tells it to be "just big enough." If you do want to set the absolute size of the JFrame yourself, call setSize() instead.
If you create your own Container, you can make it the content pane of a JFrame or JWindow by passing it to setContentPane(). Using this strategy, you could rewrite the previous example as follows:
//file: MangoMango2.java import java.awt.*; import javax.swing.*; public class MangoMango2 { public static void main(String[] args) { JFrame f = new JFrame("The Frame"); f.setLocation(100, 100); Container content = new JPanel( ); content.add(new JLabel("Mango")); content.add(new JButton("Mango")); f.setContentPane(content); f.pack( ); f.setVisible(true); } }
We'll cover labels and buttons in Chapter 16 and layouts in Chapter 18. The important thing to remember is that you can't add components directly to a JFrame or JWindow. Instead, add them to the automatically created content pane or create an entirely new content pane. If you try to add components directly to one of these containers you will get an informative runtime exception directing you to do otherwise.
We've spent a lot of time discussing the different kinds of objects in Swing—components, containers, and special containers such as frames and windows. Now it's time to discuss interobject communication in detail.
Swing objects communicate by sending events. The way we talk about events— "firing" them and "handling" them—makes it sound as if they are part of some special Java language feature. But they aren't. An event is simply an ordinary Java object that is delivered to its receiver by invoking an ordinary Java method. Everything else, however interesting, is purely convention. The entire Java event mechanism is really just a set of conventions for the kinds of descriptive objects that should be delivered; these conventions prescribe when, how, and to whom events should be delivered.
Events are sent from a single source object to one or more listeners (or receivers). A listener implements prescribed event-handling methods that enable it to receive a type of event. It then registers itself with a source of that kind of event. Sometimes an adapter object may be interposed between the event source and the listener, but in any case, registration of a listener is always established before any events are delivered.
An event object is an instance of a subclass of java.util.EventObject; it holds information about something that's happened to its source. The EventObject class itself serves mainly to identify event objects; the only information it contains is a reference to the event source (the object that sent the event). Components don't normally send or receive EventObjects as such; they work with subclasses that provide more specific information.
AWTEvent is a subclass of java.awt.EventObject; further subclasses of AWTEvent provide information about specific event types. Swing has events of its own that descend directly from EventObject. For the most part, you'll just be working with specific event subclasses from the AWT or Swing packages.
ActionEvents correspond to a decisive "action" a user has taken with the component—like pressing a button or pressing Enter. An ActionEvent thus carries the name of an action to be performed (the action command) by the program. MouseEvents are generated when a user uses the mouse within a component's area. They describe the state of the mouse and therefore carry such information as the x and y coordinates and the state of your mouse buttons at the time the MouseEvent was created.
ActionEvent operates at a higher semantic level than MouseEvent: an ActionEvent lets us know that a component has performed its job; a MouseEvent simply confers a lot of information about the mouse at a given time. You could figure out that somebody clicked on a JButton by examining mouse events, but it is simpler to work with action events. The precise meaning of an event, however, can depend on the context in which it is received.
An event is delivered by passing it as an argument to the receiving object's event-handler method. ActionEvents, for example, are always delivered to a method called actionPerformed() in the receiver:
public void actionPerformed( ActionEvent e ) { ... }
For each type of event, there is a corresponding listener interface that prescribes the method(s) it must provide to receive those events. In this case, any object that receives ActionEvents must implement the ActionListener interface:
public interface ActionListener extends java.util.EventListener { public void actionPerformed( ActionEvent e ); }
All listener interfaces are subinterfaces of java.util.EventListener, which is an empty interface. It exists only to help Java-based tools such as IDEs identify listener interfaces.
Listener interfaces are required for a number of reasons. First, they help to identify objects that can receive a given type of event. This way we can give the event-handler methods friendly, descriptive names and still make it easy for documentation, tools, and humans to recognize them in a class. Next, listener interfaces are useful because several methods can be specified for an event receiver. For example, the FocusListener interface contains two methods:
abstract void focusGained( FocusEvent e ); abstract void focusLost( FocusEvent e );
Although these methods each take a FocusEvent as an argument, they correspond to different reasons for firing the event; in this case, whether the FocusEvent means that focus was received or lost. In this case you could also figure out what happened by inspecting the event; all AWTEvents contain a constant specifying the event's type. But by using two methods, the FocusListener interface saves you the effort: if focusGained() is called, you know the event type was FOCUS_GAINED.
Similarly, the MouseListener interface defines five methods for receiving mouse events (and MouseMotionListener defines two more), each of which gives you some additional information about why the event occurred. In general, the listener interfaces group sets of related event-handler methods; the method called in any given situation provides a context for the information in the event object.
There can be more than one listener interface for dealing with a particular kind of event. For example, the MouseListener interface describes methods for receiving MouseEvents when the mouse enters or exits an area or a mouse button is pressed or released. MouseMotionListener is an entirely separate interface that describes methods to get mouse events when the mouse is moved (no buttons pressed) or dragged (buttons pressed). By separating mouse events into these two categories, Java lets you be a little more selective about the circumstances under which you want to receive MouseEvents. You can register as a listener for mouse events without receiving mouse motion events; because mouse motion events are extremely common, you don't want to handle them if you don't need to.
Two simple patterns govern the naming of Swing event listener interfaces and handler methods:
Event-handler methods are public methods that return type void and take a single event object (a subclass of java.util.EventObject) as an argument.[3]
Listener interfaces are subclasses of java.util.EventListener that are named with the suffix "Listener"—for example, MouseListener and ActionListener.
These may seem pretty obvious, but they are important because they are our first hint of a design pattern governing how to build components that work with events.
The previous section described the machinery an event receiver uses to listen for events. In this section, we'll describe how a receiver tells an event source to send it events, as they occur.
To receive events, an eligible listener must register itself with an event source. It does this by calling an "add listener" method in the event source and passing a reference to itself. (Thus, this scheme implements a callback facility.) For example, the Swing JButton class is a source of ActionEvents. Here's how a TheReceiver object might register to receive these events:
// receiver of ActionEvents class TheReceiver implements ActionListener { // source of ActionEvents JButton theButton = new JButton("Belly"); TheReceiver( ) { ... theButton.addActionListener( this ); } public void actionPerformed( ActionEvent e ) { // Belly Button pushed... }
The receiver makes a call to addActionListener() to become eligible to receive ActionEvents from the button when they occur. It passes the reference this to register itself as an ActionListener.
To manage its listeners, an ActionEvent source (like the JButton) always implements two methods:
// ActionEvent source public void addActionListener(ActionListener listener) { ... } public void removeActionListener(ActionListener listener) { ... }
The removeActionListener() method removes the listener from the list so that it will not receive future events of that kind. Swing components supply implementations of both methods; normally, you won't need to implement them yourself. It's important to pay attention to how your application uses event sources and listeners. It's okay to throw away an event source without removing its listeners, but it isn't okay to throw away listeners without removing them from the source first because the event source maintains references to them, preventing them from being garbage-collected.
Now, you may be expecting an EventSource interface listing these two methods, but there isn't one. There are no event source interfaces in the current conventions. If you are analyzing a class and trying to determine what events it generates, you have to look for the add and remove methods. For example, the presence of the addActionListener() and removeActionListener() methods define the object as a source of ActionEvents. If you happen to be a human being, you can simply look at the documentation, but if the documentation isn't available, or if you're writing a program that needs to analyze a class (a process called reflection), you can look for this design pattern.(There is a utility, the java.beans.Introspector, which can do this for you).
A source of FooEvent events for the FooListener interface must implement a pair of add/remove methods:
If an event source can support only one event listener (unicast delivery), the add listener method can throw the java.util.TooManyListenersException.
So what do all the naming patterns up to this point accomplish? Well, for one thing, they make it possible for automated tools and integrated development environments to divine sources of particular events. Tools that work with JavaBeans will use the Java reflection and introspection APIs to search for these kinds of design patterns and identify the events that can be fired by a component.
At a more concrete level, it also means that event hookups are strongly typed, just like the rest of Java. So it's impossible to accidentally hook up the wrong kind of components; for example, you can't register to receive ItemEvents from a JButton because a button doesn't have an addItemListener() method. Java knows at compile time what types of events can be delivered to whom.
Swing and AWT events are multicast; every event is associated with a single source but can be delivered to any number of receivers. When an event is fired, it is delivered individually to each listener on the list (Figure 15-3).
There are no guarantees about the order in which events are delivered. Nor are there any guarantees about what happens if you register yourself more than once with an event source; you may or may not get the event more than once. Similarly, you should assume that every listener receives the same event data. In general, events are immutable; they can't be changed by their listeners.
To be complete, we could say that event delivery is synchronous with respect to the event source, but that follows from the fact that the event delivery is really just the invocation of a normal Java method. The source of the event calls the handler method of each listener. However, listeners shouldn't assume all the events will be sent in the same thread, unless they are AWT/Swing events, which are always sent serially by a global event dispatcher thread.
All the events used by Swing GUI components are subclasses of java.util.EventObject. You can use or subclass any of the EventObject types for use in your own components. We describe the important event types here.
The events and listeners that are used by Swing fall into two packages: java.awt.event and javax.swing.event. As we've discussed, the structure of components has changed significantly between AWT and Swing. The event mechanism, however, is fundamentally the same, so the events and listeners in java.awt.event are used by Swing components. In addition, Swing has added event types and listeners in the javax.swing.event package.
java.awt.event.ComponentEvent is the base class for events that can be fired by any component. This includes events that provide notification when a component changes its dimensions or visibility as well as the other event types for mouse operations and key presses. ContainerEvents are fired by containers when components are added or removed.
MouseEvents, which track the state of the mouse, and KeyEvents, which are fired when the user uses the keyboard, are kinds of java.awt.event.InputEvents. When the user presses a key or moves the mouse within a component's area, the events are generated with that component identified as the source.
Input events and GUI events are processed in a special event queue that is managed by Swing. This gives Swing control over how all its events are delivered. First, under some circumstances, a sequence of the same type of event may be compressed into a single event. This is done to make some event types more efficient—in particular, mouse events and some special internal events used to control repainting. Perhaps more important to us, input events are delivered with extra information that lets listeners decide if the component itself should act on the event.
InputEvents come with a set of flags for special modifiers. These let you detect whether the Shift, Control, or Alt keys were held down during a mouse button or key press, and, in the case of a mouse button press, distinguish which mouse button was involved. The following are the flag values contained in java.awt.event.InputEvent:
To check for one or more flags, evaluate the bitwise AND of the complete set of modifiers and the flag or flags you're interested in. The complete set of modifiers involved in the event is returned by the InputEvent's getModifiers() method:
public void mousePressed (MouseEvent e) { int mods = e.getModifiers( ); if ((mods & InputEvent.SHIFT_MASK) != 0) { // shifted Mouse Button press } }
The three BUTTON flags can determine which mouse button was pressed on a two- or three-button mouse. If you use these, you run the risk that your program won't work on platforms without multibutton mice. Currently, BUTTON2_MASK is equivalent to ALT_MASK, and BUTTON3_MASK is equivalent to META_MASK. This means that pushing the second mouse button is equivalent to pressing the first (or only) button with the Alt key depressed, and the third button is equivalent to the first with the Meta key depressed. These provide some minimal portability even for systems that don't provide multibutton mice. However, for the most common uses of these buttons—pop-up menus—you don't have to write explicit code; Swing provides special support that automatically maps to the correct gesture in each environment (see the PopupMenu class in Chapter 16).
Java 1.4 added support for the mouse wheel. (A mouse wheel is a scrolling device in place of a middle mouse button.) By default, Swing handles mouse-wheel movement for scrollable components, so you should not have to write explicit code to handle this. Mouse wheel events are handled a little differently from other events because the conventions for using the mouse wheel don't always require the mouse to be over a scrolling component. If the immediate target component of a mouse-wheel event is not registered to receive it, a search is made for the first enclosing container that wants to consume the event. This allows components enclosed in ScrollPanes to operate as expected.
If you wish to explicitly handle mouse-wheel events, you can register to receive them using the MouseWheelListener interface shown in Table 15-1 in the next section. Mouse-wheel events encapsulate information about the amount of scrolling and the type of scroll unit, which on most systems may be configured externally to be fine-grained scroll units or large blocks. If you want a physical measure of how far the wheel was turned, you can get that with the getWheelRotation() method, which returns a number of clicks.
As we mentioned earlier, focus handling is largely done automatically in Swing applications and we'll discuss it more in Chapter 17. However, understanding how focus events are handled will help you understandand customize components.
As we described, a component can make itself eligible to receive focus using the JComponent setFocusable() method (windows may use setFocusableWindowState()). A component normally receives focus when the user clicks on it with the mouse. It can also programmatically request focus using the requestFocus() or requestFocusInWindow() methods. The requestFocusInWindow() method acts just like requestFocus() except that it does not ask for transfer across windows. (There are currently limitations on some platforms that prevent focus transfer from native applications to Java applications, so using requestFocusInWindow() guarantees portability by adding this restriction.)
Although a component can request focus explicitly, the only way to verify when a component has received or lost focus is by using the FocusListener interface (see Table 15-1 and Table 15-2 later in this chapter). You can use this interface to customize the behavior of your component when it is ready for input (e.g., the TextField's blinking cursor). Also, input components often respond to the loss of focus by committing their changes. For example, JTextFields and other components can be arranged to validate themselves when the user attempts to move to a new field and to prevent the focus change until the field is valid (as we'll see in Chapter 17).
Assuming that there is currently no focus, the following sequence of events happens when a component receives focus:
WINDOW_ACTIVATED WINDOW_GAINED_FOCUS FOCUS_GAINED
The first two are WindowEvents delivered to the component's containing Window, and the third is a FocusEvent, sent to the component itself. If a component in another window subsequently receives focus, the following complementary sequence will occur:
FOCUS_LOST WINDOW_FOCUS_LOST WINDOW_DEACTIVATED
These events carry a certain amount of context with them. The receiving component can determine from what component the focus is being transferred and from what Window. The yielding component and window are called "opposites" and are available with the FocusEvent getOppositeComponent() and WindowEvent getOppositeWindow() methods. If the opposite is part of a native non-Java application, then these values may be null.
Focus gained and lost events may also be marked as "temporary," as determined by the FocusEvent isTemporary() method. The concept of a temporary focus change is used for components such as pop-up menus, scrollbars, and window manipulation where control is expected to return to the primary component later. The distinction is made for components to "commit" or validate data upon losing focus. No commit should happen on a temporary loss of focus.
Table 15-1 and Table 15-2 summarize commonly used Swing events, which Swing components fire them, and the methods of the listener interfaces that receive them. The events and listeners are divided between the java.awt.event and javax.swing.event packages.
Event |
Fired by |
Listener interface |
Handler method |
---|---|---|---|
java.awt.event.ComponentEvent |
All components |
ComponentListener |
componentResized( ) componentMoved( ) componentShown( ) componentHidden( ) |
java.awt.event.FocusEvent |
All components |
FocusListener |
focusGained( ) |
|
|
focusLost( ) |
|
java.awt.event.KeyEvent |
All components |
KeyListener |
keyTyped( ) keyPressed( ) keyReleased( ) |
java.awt.event.MouseEvent |
All components |
MouseListener |
mouseClicked( ) mousePressed( ) mouseReleased( ) mouseEntered( ) mouseExited( ) |
|
MouseMotionListener |
mouseDragged( ) mouseMoved( ) |
|
java.awt.event.ContainerEvent |
All containers |
ContainerListener |
componentAdded( ) componentRemoved() |
Event |
Fired by |
Listener interface |
Handler method |
---|---|---|---|
java.awt.event.ActionEvent |
JButton JCheckBoxMenuItem JComboBox JFileChooser JList JRadioButtonMenuItem JTextField JToggleButton |
ActionListener |
actionPerformed( ) |
java.awt.event.AdjustmentEvent |
JScrollBar |
AdjustmentListener |
adjustmentValue-Changed( ) |
javax.swing.event.CaretEvent |
JTextComponent |
CaretListener |
caretUpdate( ) |
javax.swing.event.HyperlinkEvent |
JEditorPane, JTextPane |
HyperlinkListener |
hyperlinkUpdate( ) |
java.awt.event.InternalFrameEvent |
JInternalFrame |
InternalFrameListener |
internalFrame-Activated( ) internalFrameClosed( ) internalFrame-Closing( ) internalFrame-Deactivated( ) internalFrame-Deiconified( ) internalFrame-Iconified( ) internalFrameOpened( ) |
java.awt.event.ItemEvent |
JCheckBoxMenuItem JComboBox JRadioButtonMenuItem JToggleButton |
ItemListener |
itemStateChanged( ) |
javax.swing.event.ListDataEvent |
ListModel |
ListDataListener |
contentsChanged( ) intervalAdded( ) intervalRemoved( ) |
javax.swing.event.ListSelectionEvent |
JList ListSelectionModel |
ListSelectionListener |
valueChanged( ) |
javax.swing.event.MenuEvent |
JMenu |
MenuListener |
menuCanceled( ) menuDeselected( ) menuSelected( ) |
javax.swing.event.PopupMenuEvent |
JPopupMenu |
PopupMenuListener |
popupMenuCanceled( ) popupMenuWill-BecomeInvisible( ) popupMenuWill-BecomeVisible( ) |
javax.swing.event.MenuKeyEvent |
JMenuItem |
MenuKeyListener |
menuKeyPressed( ) menuKeyReleased( ) menuKeyTyped( ) |
javax.swing.event.MenuDragMouseEvent |
JMenuItem |
MenuDragMouseListener |
menuDragMouse-Dragged( ) menuDragMouse-Entered( ) menuDragMouse- Exited( ) menuDragMouse-Released( ) |
javax.swing.event.TableColumnModelEvent |
TableColumnModel[4] |
TableColumnModelListener |
columnAdded( ) columnMarginChanged( ) columnMoved( ) columnRemoved( ) columnSelection-Changed( ) |
javax.swing.event.TableModelEvent |
TableModel |
TableModelListener |
tableChanged( ) |
javax.swing.event.TreeExpansionEvent |
JTree |
TreeExpansionListener |
treeCollapsed( ) treeExpanded( ) |
javax.swing.event.TreeModelEvent |
TreeModel |
TreeModelListener |
treeNodesChanged( ) treeNodesInserted( ) treeNodesRemoved( ) treeStructure-Changed( ) |
javax.swing.event.TreeSelectionEvent |
JTree TreeSelectionModel |
TreeSelectionListener |
valueChanged( ) |
javax.swing.event.UndoableEditEvent |
javax.swing.text.Document |
UndoableEditListener |
undoableEdit-Happened( ) |
java.awt.event. WindowEvent |
JDialog JFrame JWindow |
WindowListener |
windowOpened( ) windowClosing( ) windowClosed( ) windowIconified( ) windowDeiconified( ) windowActivated( ) windowDeactivated( ) |
In Swing, a component's model and view are distinct. Strictly speaking, components don't fire events; models do. When you press a JButton, for example, it's actually the button's data model that fires an ActionEvent, not the button itself. But JButton has a convenience method for registering ActionListeners; this method passes its argument through to register the listener with the button model. In many cases (as with JButtons), you don't have to deal with the data model separately from the view, so we can speak loosely of the component itself firing the events. InputEvents are, of course, generated by the native input system and fired for the appropriate component, although the listener responds as though they're generated by the component.
It's not ideal to have your application components implement a bunch of listener interfaces and receive events directly. Sometimes it's not even possible. Being an event receiver forces you to modify or subclass your objects to implement the appropriate event listener interfaces and add the code necessary to handle the events. And since we are talking about Swing events here, a more subtle issue is that you would be, of necessity, building GUI logic into parts of your application that shouldn't have to know anything about the GUI. Let's look at an example.
In Figure 15-4, we have drawn the plans for our Vegomatic food processor. Here we have made our Vegomatic object implement the ActionListener interface so that it can receive events directly from the three JButton components: Chop, Puree, and Frappe. The problem is that our Vegomatic object now has to know more than how to mangle food. It also has to be aware that it is driven by three controls—specifically, buttons that send action commands—and be aware of which methods it should invoke for those commands. Our boxes labeling the GUI and application code overlap in an unwholesome way. If the marketing people should later want to add or remove buttons or perhaps just change the names, we have to be careful. We may have to modify the logic in our Vegomatic object. All is not well.
An alternative is to place an adapter class between our event source and receiver. An adapter is a simple object whose sole purpose is to map an incoming event to an outgoing method.
Figure 15-5 shows a better design that uses three adapter classes, one for each button. The implementation of the first adapter might look like:
class VegomaticAdapter1 implements ActionListener { Vegomatic vegomatic; VegomaticAdapter1 ( Vegotmatic vegomatic ) { this.vegomatic = vegomatic; } public void actionPerformed( ActionEvent e ) { vegomatic.chopFood( ); } }
So somewhere in the code where we build our GUI, we could register our listener like this:
Vegomatic theVegomatic = ...; Button chopButton = ...; // make the hookup chopButton.addActionListener( new VegomaticAdapter1(theVegomatic) );
Instead of registering itself (this) as the Button's listener, the adapter registers the Vegomatic object (theVegomatic). In this way, the adapter acts as an intermediary, hooking up an event source (the button) with an event receiver (the virtual chopper).
We have completely separated the messiness of our GUI from the application code. However, we have added three new classes to our application, none of which does very much. Is that good? It depends on your vantage point.
Under different circumstances, our buttons may have been able to share a common adapter class that was simply instantiated with different parameters. Various trade-offs can be made between size, efficiency, and elegance of code. Adapter classes will often be generated automatically by development tools. The way we have named our adapter classes VegomaticAdapter1, VegomaticAdapter2, and VegomaticAdapter3 hints at this. More often, when hand-coding, you'll use an anonymous inner class, as we'll see in the next section. At the other extreme, we can forsake Java's strong typing and use the Reflection API to create a completely dynamic hookup between an event source and its listener.
Many listener interfaces contain more than one event-handler method. Unfortunately, this means that to register yourself as interested in any one of those events, you must implement the whole listener interface. And to accomplish this you might find yourself typing in dummy "stubbed-out" methods, simply to complete the interface. There is really nothing wrong with this, but it is a bit tedious. To save you some trouble, AWT and Swing provide some helper classes that implement these dummy methods for you. For each of the most common listener interfaces containing more than one method, there is an adapter class containing the stubbed methods. You can use the adapter class as a base class for your own adapters. So when you need a class to patch together your event source and listener, you can simply subclass the adapter and override only the methods you want.
For example, the MouseAdapter class implements the MouseListener interface and provides the following implementation:
public void mouseClicked(MouseEvent e) {}; public void mousePressed(MouseEvent e) {}; public void mouseReleased(MouseEvent e) {}; public void mouseEntered(MouseEvent e) {}; public void mouseExited(MouseEvent e) {};
This isn't a tremendous time saver; it's simply a bit of sugar. The primary advantage comes into play when we use the MouseAdapter as the base for our own adapter in an anonymous inner class. For example, suppose we want to catch a mousePressed() event in some component and blow up a building. We can use the following to make the hookup:
someComponent.addMouseListener( new MouseAdapter( ) { public void MousePressed(MouseEvent e) { building.blowUp( ); } } );
We've taken artistic liberties with the formatting, but it's very readable. Moreover, we've avoided creating stub methods for the four unused event-handler methods. Writing adapters is common enough that it's nice to avoid typing those extra few lines and perhaps stave off the onset of carpal tunnel syndrome for a few more hours. Remember that any time you use an inner class, the compiler is generating a class for you, so the messiness you've saved in your source still exists in the output classes.
This topic may not be of immediate use to everyone, but sometimes an API is just interesting enough that it deserves mentioning. In Java 1.3 a class with the intriguing name java.awt.Robot was added. The AWT robot provides an API for generating input events such as keystrokes and mouse gestures programmatically. It can be used to build automated GUI testing tools and the like. The following example uses the Robot class to move the mouse to the upper left area of the screen and perform a series of events corresponding to a double click. On most Windows systems, this opens up the My Computer folder that lives in that region of the screen.
public class RobotExample { public static void main( String [] args ) throws Exception { Robot r = new Robot( ); r.mouseMove(35,35); r.mousePress( InputEvent.BUTTON1_MASK ); r.mouseRelease( InputEvent.BUTTON1_MASK ); Thread.sleep(50); r.mousePress( InputEvent.BUTTON1_MASK ); r.mouseRelease( InputEvent.BUTTON1_MASK ); } }
An important compromise was made early in the design of Swing relating to speed, GUI consistency, and thread safety. To provide maximum performance and simplicity in the common case, Swing does not explicitly synchronize access to most Swing component methods. This means that most Swing components are, technically, not thread-safe for multithreaded applications. Now don't panic: it's not as bad as it sounds because there is a plan. All event processing in AWT/Swing is handled by a single, system thread using a single system event queue. The queue serves two purposes. First, it eliminates thread-safety issues by making all GUI modifications happen in a single thread. Second, the queue imposes a strict ordering of all activity in Swing. Because painting is handled in Swing using events, all screen updating is also ordered with respect to all event handling.
What this means for you is that multithreading programs need to be careful about how they update Swing components after they are realized (added to a visible container). If you make arbitrary modifications to Swing components from your own threads, you run the risk of malformed rendering on the screen and inconsistent behavior.
There are several conditions under which it is always safe to modify a Swing component. First, Swing components can be modified before they are realized. The term realized originates from the days when the component would have created its peer object. It is the point when it is added to a visible container or when it is made visible in the case of a window. Most of our examples in this book set up GUI components in their main() method, add them to a JFrame, and then, as their final action cause the JFrame to be displayed using setVisible(). This style of setup is safe because components are not realized until the container is made visible. Actually, that last sentence is not entirely true. Technically, components can also be realized by the JFrame() pack() method. However since no GUI is shown until the container is made visible, it is unlikely that any GUI activity can be mishandled.
Second, it's safe to modify Swing components from code that is already running from the system event handler's thread. Because all events are processed by the event queue, the methods of all Swing event listeners are normally invoked by the system event handling thread. This means that event handler methods and, transitively, any methods called from those methods during the lifetime of that call can freely modify Swing GUI components because they are already running in the system event-dispatching thread. If you are unsure of whether some bit of code will ever be called outside the normal event thread, you can use the static method SwingUtilities.isEventDispatchThread() to test the identity of the current thread. You can then perform your activity using the alternate mechanism we'll talk about later.
Finally, Swing components can be safely modified when the API documentation explicitly says that the method is thread-safe. Many important methods of Swing components are explicitly documented as thread-safe. These include the JComponent repaint() and revalidate() methods, many methods of Swing text components, and all listener add and remove methods.
If you can't meet any of the requirements for thread safety listed previously, you can use a simple API to get the system event queue to perform arbitrary activities for you on the event handling thread. This is accomplished using the invokeAndWait() or invokeLater() static methods of the javax.swing.SwingUtilities class.
Use this method to ask Swing to execute the run() method of the specified Runnable.
This method is just like invokeLater() except that it waits until the run() method has completed before returning.
You can put whatever activities you want inside a Runnable object and cause the event dispatcher to perform them using these methods. Often you'll use an inner class, for example:
SwingUtilities.invokeLater( new Runnable( ) { public void run( ) { MyComponent.setVisible(false); } } );
You may find that you won't have to use the event dispatcher in simple GUI applications because most activity happens in response to user interface events where it is safe to modify components. However, consider these caveats when you create threads to perform long-running tasks such as loading data or communicating over the network.
[1] Don't be fooled by the javax prefix, which usually denotes a standard extension API. Swing is an integral part of the core APIs in Java 2 Standard Edition.
[2] In Chapter 10 we described the Observer class and Observable interface of the java.util package. Swing doesn't use these classes directly, but it does use exactly the same design pattern for handling event sources and listeners.
[3] This rule is not complete. The JavaBeans conventions (see Chapter 19) allows event-handler methods to take additional arguments when absolutely necessary and also to throw checked exceptions.
[4] The TableColumnModel class breaks with convention in the names of the methods that add listeners. They are addColumnModelListener( ) and removeColumnModelListener( ).
CONTENTS |