Swing is Java’s user interface
toolkit. It was developed during the life of SDK 1.1 and now is part
of the core APIs in Java 2 (née JDK 1.2). Swing provides
classes representing interface items like windows, buttons, combo
boxes, trees, grids, 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.[40]
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 new 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 14, discusses the basic components from which user interfaces are built: lists, text fields, checkboxes, and so on.
Chapter 15, dives further into the Swing toolkit, describing text components, trees, tables, and other neat stuff.
Chapter 16 discusses layout managers, which are responsible for arranging components within a window.
Chapter 17 discusses the fundamentals of drawing, including simple image displays.
Chapter 18, 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
Robert Eckstein, Marc Loy, and Dave Wood (O’Reilly &
Associates). 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 13.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. Its classes and functionality are the same for all Java implementations, so Java applications built with AWT should work in the same way on all platforms. You could choose to write your code under Windows, and 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 was lacking advanced user interface components, like trees and grids.
Swing takes a fundamentally different approach. Instead of using native toolkits to supply interface items like buttons and combo boxes, components in Swing are implemented in Java itself. This means that, whatever platform you’re using, a Swing button (for example) looks the same. It also makes Swing much less prone to platform-specific bugs, which were a problem for AWT.
If you already know AWT, you’ll be able to transfer a lot of your knowledge into the Swing world. However, there’s a lot more material in Swing than in AWT, so be prepared to learn. If you’ve never programmed with AWT, this chapter and the next two provide a gentle introduction to building user interfaces with Swing.
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 on 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,
drawing canvases, 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 13.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. Intuitively, we can say that
Swing is a very large extension to AWT.
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 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, like
Button
and JButton
. But Swing
is much more than simply a replacement for AWT—it contains
sophisticated components, like trees and grids, 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,
like font and color. The JComponent
class also
contains methods implemented by specific subclasses to produce the
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. This includes
methods for accessing graphics and for working with off-screen
drawing buffers for the display.
By a “component’s behavior,”
we mean the way it responds to user-driven events. When the user
performs an action (like 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 interested. Specific types of
events cover different categories of component user interaction. For
example,
MouseEvent
s describe activities of the
mouse within a component’s area,
KeyEvent
s describe key presses, and
higher-level events (such as ActionEvent
s)
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 enables containers to take on certain responsibilities for the components that they hold. Instead of every component listening for and handling events for its own bit of the user interface, a container may register itself or another object to receive the events for its child components and “glue” those events to the correct application logic.
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 you’ve seen, Swing components are also containers.
Containers can manage and arrange JComponent
objects without knowing what they are and what they are doing.
Components can be swapped and replaced with new versions easily and
combined into composite user interface objects that can be 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. The cold truth is that 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 is a nasty place full of architecture dependence, local peculiarities, and strange physical devices like mice, trackballs, and ’69 Buicks.
At some level, our components will 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 of the components in Swing are written in pure Java, with no native code involved. In Swing, only top-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. Having far fewer interactions with the underlying windowing system than AWT, Swing is more reliable.
Another consquence of using lightweight components is that it is easy to change the appearance of components. Since each component draws itself, instead of relying on a peer, it can decide at runtime how to draw 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 and Solaris are available, as well as an entirely original one called Metal, which is the default scheme.
Java’s developers initially decided to implement the standard AWT components with a “mostly native” toolkit. As we described earlier, that means that most of the important functionality of these classes is delegated to peer objects, which live in the native operating system. Using native peers allows Java to take on the look-and-feel of the local operating environment. Macintosh users see Mac applications, PC users see Windows’ windows, and Unix users can have their Motif motif; warm fuzzy feelings abound. Java’s chameleon-like ability to blend into the native environment was considered by many to be an integral part of platform independence. However, there are a few important downsides to this arrangement.
First, using native peer implementations makes it much more difficult
(if not impossible) to subclass these
components to
specialize or modify their behavior. Most of their behavior comes
from the native peer, and therefore can’t be overridden or
extended easily. As it turns out, this is not a terrible problem
because of the ease with which we can make our own components in
Java. It is also true that a sophisticated new component, like an
HTML viewer, would benefit little in deriving from a more primitive
text-viewing component like TextArea
.
Next, as we mentioned before, porting the native code makes it much more difficult to bring Java to a new platform. For the user, this can only mean one thing—bugs. Basically, there were too many places where AWT was interacting with the underlying windowing system—one peer per component. There were too many places where something might go wrong, or peers might behave in subtly different ways on different platforms.
Finally, we come to a somewhat counterintuitive problem with the use of native peers. In most current implementations of Java, the native peers are quite “heavy” and consume a lot of resources. You might expect that relying on native code would be much more efficient than creating the components in Java. However, it can take a long time to instantiate a large number of GUI elements when each requires the creation of a native peer from the toolkit. And in some cases you may find that once the native peers are created, they don’t perform as well as the pure Java equivalents that you can create yourself.
An extreme example would be a spreadsheet that uses an AWT
TextField
for each cell. Creating hundreds of
TextFieldPeer
objects would be something between
slow and impossible. While simply saying “don’t do
that” might be a valid answer, this prompts the question: how
do you create large applications with complex GUIs? The answer, of
course, is Swing. Swing’s peerless architecture means that
it’s possible to build large, complicated user interfaces and
have them work efficiently.
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-press 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). When an observable changes or performs a function, it notifies all of 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.[41]
In an event-driven environment like 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 re-expose it, a Swing thread asks 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, and 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, when
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( )
will be 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 will try to repaint the component
within the time you specify. An application can use this method to
govern its refresh rate. For example, the rate at which you render
frames for an animation might vary, depending on other factors (like
the complexity of the image). You could impose an effective maximum
frame rate by calling repaint( )
with a time (the
inverse of the frame rate) as an argument. If you then happen to make
more than one repaint request within that time period, Swing is not
obliged to physically repaint for each one. It can simply condense
them to carry out a single update within the time you have specified.
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 another, 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( )
take 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.)
All components paint and update themselves using this mechanism.
Because all Swing components are peerless, it’s easy to draw on
any of them. (With an AWT component, the presence of the native peer
component can make such drawing operations difficult.) In practice,
it won’t make sense very often to draw on the prebuilt
components, like buttons and list boxes. When creating your own
components, you’ll probably just subclass
JComponent
directly.
Standard Swing
components can be turned on and off
by calling the setEnabled( )
method. When a component like 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. This is one of the things that used to be
unpredictable in AWT, because of the peers involved. In Swing, it
just works.
In order to receive keyboard events, a
component has to have keyboard focus. The
component with the focus is simply the currently selected input
component. It receives all keyboard event information until the focus
changes. A component can ask for focus with the
JComponent
’s requestFocus( )
method. Text components like
JTextField
and JTextArea
do
this automatically whenever you click the mouse in their area. A
component can find out when it gains or loses focus through the
FocusListener
interface (see Table 13.1 and Table 13.2
later in this chapter). If you want to create your own text-oriented
component, you could implement this behavior yourself. For instance,
you might request focus when the mouse is clicked in your
component’s area. After receiving focus, you could change the
cursor or do something else to highlight the component.
Many user interfaces are designed so that the focus automatically
jumps to the “next available” component when the user
presses the Tab key. This behavior is particularly common in forms;
users often expect to be able to tab to the next text entry field.
Swing handles automatic focus traversal for you. You can get control
over the behavior through the transferFocus( )
,
setNextFocusable-Component( )
, and
setFocusTraversable( )
methods of
JComponent
. The method transferFocus( )
passes the focus to the next appropriate component. If
you want to change the traversal order, you can call
setNextFocusable-Component( )
to tell each
component which component should be next. The
setFocus-Traversable( )
accepts a
boolean
value that determines whether the
component should be considered eligible for receiving focus. You can
use this method to determine whether your components can be tabbed
to.
The JComponent
class is very large; it has to provide
the base-level functionality for all of 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:
-
Container
getParent
( )
Return the container that holds this component.
-
String
getName
( )
-
void
setName
(String
name
)
Get or assign the
String
name of this component. Naming a component is useful for debugging. The name is returned bytoString( )
.-
void
setVisible
(boolean
visible
)
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.
-
Color
getForeground
( )
-
void
setForeground
(Color
c
)
-
Color
getBackground
( )
-
void
setBackground
(Color
c
)
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; it is also the default drawing color for the
Graphics
object passed to the component’spaint( )
andpaintComponent( )
methods. The background color is used to fill the component’s area when it is cleared by the default implementation ofupdate( )
.-
Dimension
getSize
( )
-
void
setSize
(int
width
, int
height
)
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 inJComponent
to set its location, but normally this is the job of a layout manager.-
Dimension
getPreferredSize
( )
-
void
setPreferredSize
(Dimension
preferredSize
)
Use these methods to examine or set the preferred size of a component. Layout managers attempt to set components to their preferred sizes. If you change a component’s preferred size, remember to call
revalidate( )
on the component to get it laid out again.-
Cursor
getCursor
( )
-
void
setCursor
(Cursor
cursor
)
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 );
[40] Don’t
be fooled by the javax
prefix, which usually
denotes a standard extension API. Swing is part of the core APIs in
Java 2; every Java 2 implementation includes Swing.
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.