To this point, you have seen only how to write programs that take input from the keyboard, fuss with it, and then display the results on a console screen. This is not what most users want now. Modern programs don’t work this way and neither do web pages. This chapter starts you on the road to writing Java programs that use a graphical user interface (GUI). In particular, you learn how to write programs that size and locate windows on the screen, display text with multiple fonts in a window, display images, and so on. This gives you a useful, valuable repertoire of skills that you will put to good use in subsequent chapters as you write interesting programs.
The next two chapters show you how to process events, such as keystrokes and mouse clicks, and how to add interface elements, such as menus and buttons, to your applications. When you finish these three chapters, you will know the essentials for writing stand-alone graphical applications. Chapter 10 shows how to program applets that use these features and are embedded in web pages. For more sophisticated graphics programming techniques, we refer you to Volume 2.
When Java 1.0 was introduced, it contained a class library, which Sun called the Abstract Window Toolkit (AWT), for basic GUI programming. The basic AWT library deals with user interface elements by delegating their creation and behavior to the native GUI toolkit on each target platform (Windows, Solaris, Macintosh, and so on). For example, if you used the original AWT to put a text box on a Java window, an underlying “peer” text box actually handled the text input. The resulting program could then, in theory, run on any of these platforms, with the “look and feel” of the target platform—hence Sun’s trademarked slogan “Write Once, Run Anywhere.”
The peer-based approach worked well for simple applications, but it soon became apparent that it was fiendishly difficult to write a high-quality portable graphics library that depended on native user interface elements. User interface elements such as menus, scrollbars, and text fields can have subtle differences in behavior on different platforms. It was hard, therefore, to give users a consistent and predictable experience with this approach. Moreover, some graphical environments (such as X11/Motif) do not have as rich a collection of user interface components as does Windows or the Macintosh. This in turn further limits a portable library based on peers to a “lowest common denominator” approach. As a result, GUI applications built with the AWT simply did not look as nice as native Windows or Macintosh applications, nor did they have the kind of functionality that users of those platforms had come to expect. More depressingly, there were different bugs in the AWT user interface library on the different platforms. Developers complained that they needed to test their applications on each platform, a practice derisively called “write once, debug everywhere.”
In 1996, Netscape created a GUI library they called the IFC (Internet Foundation Classes) that used an entirely different approach. User interface elements, such as buttons, menus, and so on, were painted onto blank windows. The only peer functionality needed was a way to put up windows and to paint on the window. Thus, Netscape’s IFC widgets looked and behaved the same no matter which platform the program ran on. Sun worked with Netscape to perfect this approach, creating a user interface library with the code name “Swing” (sometimes called the “Swing set”). Swing was available as an extension to Java 1.1 and became a part of the standard library in JDK 1.2.
Since, as Duke Ellington said, “It Don’t Mean a Thing If It Ain’t Got That Swing,” Swing is now the official name for the non-peer-based GUI toolkit. Swing is part of the Java Foundation Classes (JFC). The full JFC is vast and contains far more than the Swing GUI toolkit. JFC features not only include the Swing components but also an accessibility API, a 2D API, and a drag-and-drop API.
Swing is not a complete replacement for the AWT—it is built on top of the AWT architecture. Swing simply gives you more capable user interface components. You use the foundations of the AWT, in particular, event handling, whenever you write a Swing program. From now on, we say “Swing” when we mean the “painted” non-peer-based user interface classes, and we say “AWT” when we mean the underlying mechanisms of the windowing toolkit, such as event handling.
Of course, Swing-based user interface elements will be somewhat slower to appear on the user’s screen than the peer-based components used by the AWT. Our experience is that on any reasonably modern machine, the speed difference shouldn’t be a problem. On the other hand, the reasons to choose Swing are overwhelming:
Swing has a rich and convenient set of user interface elements.
Swing has few dependencies on the underlying platform; it is therefore less prone to platform-specific bugs.
Swing gives a consistent user experience across platforms.
All this means Swing has the potential of fulfilling the promise of Sun’s “Write Once, Run Anywhere” slogan.
Still, the third plus is also a potential drawback: if the user interface elements look the same on all platforms, then they will look different from the native controls and thus users will be less familiar with them.
Swing solves this problem in a very elegant way. Programmers writing Swing programs can give the program a specific “look and feel.” For example, Figure 7–1 and 7–2 show the same program running with the Windows[1] and the Motif look and feel.
Although we won’t have space in this book to tell you how to do it, Java programmers can extend an existing look and feel or even design a totally new look and feel. This is a tedious process that involves specifying how each Swing component is painted. Some developers have done just that, especially when porting Java to nontraditional platforms such as kiosk terminals or handheld devices. See http://www.javootoo.com for a collection of interesting look-and-feel implementations.
JDK 5.0 introduces a new look and feel, called Synth, that makes this process easier. In Synth, you can define a new look and feel by providing image files and XML descriptors, without doing any programming.
Furthermore, Sun developed a platform-independent look and feel that was called “Metal” until the marketing folks renamed it as the “Java look and feel.” However, most programmers continue to use the term “Metal,” and we will do the same in this book.
Some people criticized Metal as being stodgy, and the look was freshened up for the 5.0 release (see Figure 7–3). Now the Metal look supports multiple themes—minor variations in colors and fonts. The default theme is called “Ocean.” In this book, we use Swing, with the Metal look and feel and the Ocean theme, for all our graphical programs.
Most Java user interface programming is nowadays done in Swing, with one notable exception. The Eclipse integrated development environment uses a graphics toolkit called SWT that is similar to the AWT, mapping to native components on various platforms. You can find articles describing SWT at http://www.eclipse.org/articles/.
Finally, we do have to warn you that if you have programmed Microsoft Windows applications with Visual Basic or C#, you know about the ease of use that comes with the graphical layout tools and resource editors these products provide. These tools let you design the visual appearance of your application, and then they generate much (often all) of the GUI code for you. Although GUI builders are available for Java programming, these products are not as mature as the corresponding tools for Windows. In any case, to fully understand graphical user interface programming (or even, we feel, to use these tools effectively), you need to know how to build a user interface manually. Naturally, this often requires writing a lot of code.
A top-level window (that is, a window that is not contained inside another window) is called a frame in Java. The AWT library has a class, called Frame
, for this top level. The Swing version of this class is called JFrame
and extends the Frame
class. The JFrame
is one of the few Swing components that is not painted on a canvas. Thus, the decorations (buttons, title bar, icons, and so on) are drawn by the user’s windowing system, not by Swing.
Most Swing component classes start with a “J”: JButton
, JFrame
, and so on. There are classes such as Button
and Frame
, but they are AWT components. If you accidentally omit a “J”, your program may still compile and run, but the mixture of Swing and AWT components can lead to visual and behavioral inconsistencies.
In this section, we go over the most common methods for working with a Swing JFrame. Example 7–1 lists a simple program that displays an empty frame on the screen, as illustrated in Figure 7–4.
Example 7–1. SimpleFrameTest.java
1. import javax.swing.*; 2. 3. public class SimpleFrameTest 4. { 5. public static void main(String[] args) 6. { 7. SimpleFrame frame = new SimpleFrame(); 8. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 9. frame.setVisible(true); 10. } 11. } 12. 13. class SimpleFrame extends JFrame 14. { 15. public SimpleFrame() 16. { 17. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 18. } 19. 20. public static final int DEFAULT_WIDTH = 300; 21. public static final int DEFAULT_HEIGHT = 200; 22. }
Let’s work through this program, line by line.
The Swing classes are placed in the javax.swing
package. The package name javax
indicates a Java extension package, not a core package. The Swing classes are indeed an extension to Java 1.1. Because the Swing classes were not made a part of the core hierarchy, it is possible to load the Swing classes into a Java 1.1-compatible browser. (The security manager of the browser does not allow adding any packages that start with “java.
”.) On the Java 2 platform, the Swing package is no longer an extension but is instead part of the core hierarchy. Any Java implementation that is compatible with Java 2 must supply the Swing classes. Nevertheless, the javax
name remains, for compatibility with Java 1.1 code. (Actually, the Swing package started out as com.sun.java.swing
, then was briefly moved to java.awt.swing
during early Java 2 beta versions, then went back to com.sun.java.swing
in late Java 2 beta versions, and after howls of protest by Java programmers, found its final resting place in javax.swing
.)
By default, a frame has a rather useless size of 0 × 0 pixels. We define a subclass SimpleFrame
whose constructor sets the size to 300 × 200 pixels. In the main
method of the SimpleFrameTest
class, we start out by constructing a SimpleFrame
object.
Next, we define what should happen when the user closes this frame. For this particular program, we want the program to exit. To select this behavior, we use the statement
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
In other programs with multiple frames, you would not want the program to exit just because the user closes one of the frames. By default, a frame is hidden when the user closes it, but the program does not terminate.
Simply constructing a frame does not automatically display it. Frames start their life invisible. That gives the programmer the chance to add components into the frame before showing it for the first time. To show the frame, the main
method calls the setVisible
method of the frame.
Afterwards, the main
method exits. Note that exiting main
does not terminate the program, just the main thread. Showing the frame activates a user interface thread that keeps the program alive.
Before JDK 5.0, it was possible to use the show
method that the JFrame
class inherits from the superclass Window
. The Window
class has a superclass Component
that also has a show
method. The Component.show
method was deprecated in JDK 1.2. You are supposed to call setVisible(true)
instead if you want to show a component. However, until JDK 1.4, the Window.show
method was not deprecated. In fact, it was quite useful, making the window visible and bringing it to the front. Sadly, that benefit was lost on the deprecation police, and JDK 5.0 deprecates the show
method for windows as well.
The running program is shown in Figure 7–4 on page 249—it is a truly boring top-level window. As you can see in the figure, the title bar and surrounding decorations, such as resize corners, are drawn by the operating system and not the Swing library. If you run the same program in X Windows, the frame decorations are different. The Swing library draws everything inside the frame. In this program, it just fills the frame with a default background color.
In the preceding example we wrote two classes, one to define a frame class and one that contains a main
method that creates and shows a frame object. You will frequently see programs in which the main
method is opportunistically tossed into a convenient class, like this:
class SimpleFrame extends JFrame { public static void main(String[] args) { SimpleFrame frame = new SimpleFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } public SimpleFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; }
Using the main
method of the frame class for the code that launches the program is simpler in one sense. You do not have to introduce another auxiliary class. However, quite a few programmers find this code style a bit confusing. Therefore, we prefer to separate out the class that launches the program from the classes that define the user interface.
The JFrame
class itself has only a few methods for changing how frames look. Of course, through the magic of inheritance, most of the methods for working with the size and position of a frame come from the various superclasses of JFrame
. Among the most important methods are the following ones:
The dispose
method that closes the window and reclaims any system resources used in creating it;
The setIconImage
method, which takes an Image
object to use as the icon when the window is minimized (often called iconized in Java terminology);
The setTitle
method for changing the text in the title bar;
The setResizable
method, which takes a boolean
to determine if a frame will be resizeable by the user.
Figure 7–5 illustrates the inheritance hierarchy for the JFrame
class.
As the API notes indicate, the Component
class (which is the ancestor of all GUI objects) and the Window
class (which is the superclass of the Frame
class) are where you need to look to find the methods to resize and reshape frames. For example, the setLocation
method in the Component
class is one way to reposition a component. If you make the call
setLocation(x, y)
the top-left corner is located x
pixels across and y
pixels down, where (0, 0) is the top-left corner of the screen. Similarly, the setBounds
method in Component
lets you resize and relocate a component (in particular, a JFrame
) in one step, as
setBounds(x, y, width, height)
For a frame, the coordinates of the setLocation
and setBounds
are taken relative to the whole screen. As you will see in Chapter 9, for other components inside a container, the measurements are taken relative to the container.
Remember: if you don’t explicitly size a frame, all frames will default to being 0 by 0 pixels. To keep our example programs simple, we resize the frames to a size that we hope works acceptably on most displays. However, in a professional application, you should check the resolution of the user’s screen and write code that resizes the frames accordingly: a window that looks nice on a laptop screen will look like a postage stamp on a high-resolution screen. As you will soon see, you can obtain the screen dimensions in pixels on the user’s system. You can then use this information to compute the optimal window size for your program.
The API notes for this section give what we think are the most important methods for giving frames the proper look and feel. Some of these methods are defined in the JFrame
class. Others come from the various superclasses of JFrame
. At some point, you may need to search the API docs to see if there are methods for some special purpose. Unfortunately, that is a bit tedious to do with the JDK documentation. For subclasses, the API documentation only explains overridden methods. For example, the toFront
method is applicable to objects of type JFrame
, but because it is simply inherited from the Window
class, the JFrame
documentation doesn’t explain it. If you feel that there should be a method to do something and it isn’t explained in the documentation for the class you are working with, try looking at the API documentation for the methods of the superclasses of that class. The top of each API page has hyperlinks to the superclasses, and inherited methods are listed below the method summary for the new and overridden methods.
To give you an idea of what you can do with a window, we end this section by showing you a sample program that positions one of our closable frames so that
Its area is one-fourth that of the whole screen;
It is centered in the middle of the screen.
For example, if the screen was 800 × 600 pixels, we need a frame that is 400 × 300 pixels and we need to move it so the top left-hand corner is at (200,150).
To find out the screen size, use the following steps. Call the static getDefaultToolkit
method of the Toolkit
class to get the Toolkit
object. (The Toolkit
class is a dumping ground for a variety of methods that interface with the native windowing system.) Then call the getScreenSize
method, which returns the screen size as a Dimension
object. A Dimension
object simultaneously stores a width and a height, in public (!) instance variables width
and height
. Here is the code:
Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); int screenWidth = screenSize.width; int screenHeight = screenSize.height;
We also supply an icon. Because the representation of images is also system dependent, we again use the toolkit to load an image. Then, we set the image as the icon for the frame.
Image img = kit.getImage("icon.gif"); setIconImage(img);
Depending on your operating system, you can see the icon in various places. For example, in Windows, the icon is displayed in the top-left corner of the window, and you can see it in the list of active tasks when you press ALT+TAB.
Example 7–2 is the complete program. When you run the program, pay attention to the “Core Java” icon.
It is quite common to set the main frame of a program to the maximum size. As of JDK 1.4, you can simply maximize a frame by calling
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
If you write an application that takes advantage of multiple display screens, you should use the GraphicsEnvironment
and GraphicsDevice
classes to find the dimensions of the display screens. As of JDK 1.4, the GraphicsDevice
class also lets you execute your application in full-screen mode.
Example 7–2. CenteredFrameTest.java
1. /**import java.awt.*; 2. import java.awt.event.*; 3. import javax.swing.*; 4. 5. public class CenteredFrameTest 6. { 7. public static void main(String[] args) 8. { 9. CenteredFrame frame = new CenteredFrame(); 10. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 11. frame.setVisible(true); 12. } 13. } 14. 15. class CenteredFrame extends JFrame 16. { 17. public CenteredFrame() 18. { 19. // get screen dimensions 20. 21. Toolkit kit = Toolkit.getDefaultToolkit(); 22. Dimension screenSize = kit.getScreenSize(); 23. int screenHeight = screenSize.height; 24. int screenWidth = screenSize.width; 25. 26. // center frame in screen 27. 28. setSize(screenWidth / 2, screenHeight / 2); 29. setLocation(screenWidth / 4, screenHeight / 4); 30. 31. // set frame icon and title 32. 33. Image img = kit.getImage("icon.gif"); 34. setIconImage(img); 35. setTitle("CenteredFrame"); 36. } 37. }
boolean isVisible()
checks whether this component is set to be visible. Components are initially visible, with the exception of top-level components such as JFrame
.
void setVisible(boolean b)
shows or hides the component depending on whether b
is true
or false
.
boolean isShowing()
checks whether this component is showing on the screen. For this, it must be visible and be inside a container that is showing.
boolean isEnabled()
checks whether this component is enabled. An enabled component can receive keyboard input. Components are initially enabled.
void setEnabled(boolean b)
enables or disables a component.
Point getLocation()
1.1
returns the location of the top-left corner of this component, relative to the top-left corner of the surrounding container. (A Point
object p
encapsulates an x- and a y-coordinate which are accessible by p.x
and p.y
.)
Point getLocationOnScreen()
1.1
returns the location of the top-left corner of this component, using the screen’s coordinates.
void setBounds(int x, int y, int width, int height)
1.1
moves and resizes this component. The location of the top-left corner is given by x
and y
, and the new size is given by the width
and height
parameters.
void setLocation(int x, int y)
1.1
void setLocation(Point p)
1.1
move the component to a new location. The x- and y-coordinates (or p.x
and p.y
) use the coordinates of the container if the component is not a top-level component, or the coordinates of the screen if the component is top level (for example, a JFrame
).
Dimension getSize()
1.1
gets the current size of this component.
void setSize(int width, int height)
1.1
void setSize(Dimension d)
1.1
resize the component to the specified width and height.
void toFront()
shows this window on top of any other windows.
void toBack()
moves this window to the back of the stack of windows on the desktop and rearranges all other visible windows accordingly.
void setResizable(boolean b)
determines whether the user can resize the frame.
void setTitle(String s)
sets the text in the title bar for the frame to the string s
.
void setIconImage(Image image)
Parameters: |
| The image you want to appear as the icon for the frame |
void setUndecorated(boolean b)
1.4
removes the frame decorations if b
is true
.
boolean isUndecorated()
1.4
returns true
if this frame is undecorated.
int getExtendedState()
1.4
void setExtendedState(int state)
1.4
get or set the window state. The state is one of
Frame.NORMAL Frame.ICONIFIED Frame.MAXIMIZED_HORIZ Frame.MAXIMIZED_VERT Frame.MAXIMIZED_BOTH
static Toolkit getDefaultToolkit()
returns the default toolkit.
Dimension getScreenSize()
gets the size of the user’s screen.
Image getImage(String filename)
loads an image from the file with name filename
.
In this section, we show you how to display information inside a frame. For example, rather than displaying “Not a Hello, World program” in text mode in a console window as we did in Chapter 3, we display the message in a frame, as shown in Figure 7–6.
You could draw the message string directly onto a frame, but that is not considered good programming practice. In Java, frames are really designed to be containers for components such as a menu bar and other user interface elements. You normally draw on another component, called a panel, which you add to the frame.
The structure of a JFrame
is surprisingly complex. Look at Figure 7–7, which shows the makeup of a JFrame
. As you can see, four panes are layered in a JFrame
. The root pane, layered pane, and glass pane are of no interest to us; they are required to organize the menu bar and content pane and to implement the look and feel. The part that most concerns Swing programmers is the content pane. When designing a frame, you add components into the content pane, using code such as the following:
Container contentPane = frame.getContentPane(); Component c = . . .; contentPane.add(c);
Up to JDK 1.4, the add
method of the JFrame
class was defined to throw an exception with the message “Do not use JFrame.add()
. Use JFrame.getContentPane().add()
instead”. As of JDK 5.0, the JFrame.add
method has given up trying to reeducate programmers, and it simply calls add
on the content pane.
Thus, as of JDK 5.0, you can simply use the call
frame.add(c);
In our case, we want to add a single panel to the frame onto which we will draw our message. Panels are implemented by the JPanel
class. They are user interface elements with two useful properties:
They have a surface onto which you can draw.
They themselves are containers.
Thus, they can hold other user interface components such as buttons, sliders, and so on.
To make a panel more interesting, you use inheritance to create a new class, and then override or add methods to get the extra functionality you want.
In particular, to draw on a panel, you
The paintComponent
method is actually in JComponent
—the superclass for all nonwindow Swing components. It takes one parameter of type Graphics
. A Graphics
object remembers a collection of settings for drawing images and text, such as the font you set or the current color. All drawing in Java must go through a Graphics
object. It has methods that draw patterns, images, and text.
The Graphics parameter is similar to a device context in Windows or a graphics context in X11 programming.
Here’s how to make a panel onto which you can draw:
class MyPanel extends JPanel { public void paintComponent(Graphics g) { . . . // code for drawing will go here } }
Each time a window needs to be redrawn, no matter what the reason, the event handler notifies the component. This causes the paintComponent
methods of all components to be executed.
Never call the paintComponent
method yourself. It is called automatically whenever a part of your application needs to be redrawn, and you should not interfere with this automatic process.
What sorts of actions trigger this automatic response? For example, painting occurs because the user increased the size of the window or minimized and then restored the window. If the user popped up another window and it covered an existing window and then made the overlaid window disappear, the application window that was covered is now corrupted and will need to be repainted. (The graphics system does not save the pixels underneath.) And, of course, when the window is displayed for the first time, it needs to process the code that specifies how and where it should draw the initial elements.
If you need to force repainting of the screen, call the repaint
method instead of paintComponent
. The repaint
method will cause paintComponent
to be called for all components, with a properly configured Graphics
object.
As you saw in the code fragment above, the paintComponent
method takes a single parameter of type Graphics
. Measurement on a Graphics
object for screen display is done in pixels. The (0, 0) coordinate denotes the top-left corner of the component on whose surface you are drawing.
Displaying text is considered a special kind of drawing. The Graphics
class has a drawString
method that has the following syntax:
g.drawString(text, x, y)
In our case, we want to draw the string "Not a Hello, World Program"
in our original window, roughly one-quarter of the way across and halfway down. Although we don’t yet know how to measure the size of the string, we’ll start the string at coordinates (75, 100). This means the first character in the string will start at a position 75 pixels to the right and 100 pixels down. (Actually, it is the baseline for the text that is 100 pixels down—see below for more on how text is measured.) Thus, our paintComponent
method looks like this:
class NotHelloWorldPanel extends JPanel { public void paintComponent(Graphics g) { . . . // see below g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y); } public static final int MESSAGE_X = 75; public static final int MESSAGE_Y = 100; }
However, this paintComponent
method is not complete. The NotHelloWorldPanel
class extends the JPanel
class, which has its own idea of how to paint the panel, namely, to fill it with the background color. To make sure that the superclass does its part of the job, we must call super.paintComponent
before doing any painting on our own.
class NotHelloWorldPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); . . . // code for drawing will go here } }
Example 7–3 shows the complete code. If you use JDK 1.4 or below, remember to change the call add(panel)
to getContentPane().add(panel)
.
Example 7–3. NotHelloWorld.java
1. import javax.swing.*; 2. import java.awt.*; 3. 4. public class NotHelloWorld 5. { 6. public static void main(String[] args) 7. { 8. NotHelloWorldFrame frame = new NotHelloWorldFrame(); 9. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 10. frame.setVisible(true); 11. } 12. } 13. 14. /** 15. A frame that contains a message panel 16. */ 17. class NotHelloWorldFrame extends JFrame 18. { 19. public NotHelloWorldFrame() 20. { 21. setTitle("NotHelloWorld"); 22. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 23. 24. // add panel to frame 25. 26. NotHelloWorldPanel panel = new NotHelloWorldPanel(); 27. add(panel); 28. } 29. 30. public static final int DEFAULT_WIDTH = 300; 31. public static final int DEFAULT_HEIGHT = 200; 32. } 33. 34. /** 35. A panel that displays a message. 36. */ 37. class NotHelloWorldPanel extends JPanel 38. { 39. public void paintComponent(Graphics g) 40. { 41. super.paintComponent(g); 42. 43. g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y); 44. } 45. 46. public static final int MESSAGE_X = 75; 47. public static final int MESSAGE_Y = 100; 48. }
Container getContentPane()
returns the content pane object for this JFrame
.
void add(Component c)
adds the given component to the content pane of this frame. (Before JDK 5.0, this method threw an exception.)
void repaint()
causes a repaint of the component “as soon as possible.”
public void repaint(int x, int y, int width, int height)
causes a repaint of a part of the component “as soon as possible.”
void paintComponent(Graphics g)
override this method to describe how your component needs to be painted.
Since JDK version 1.0, the Graphics
class had methods to draw lines, rectangles, ellipses, and so on. But those drawing operations are very limited. For example, you cannot vary the line thickness and you cannot rotate the shapes.
JDK 1.2 introduced the Java 2D library, which implements a powerful set of graphical operations. In this chapter, we only look at the basics of the Java 2D library—see the Advanced AWT chapter in Volume 2 for more information on the advanced features.
To draw shapes in the Java 2D library, you need to obtain an object of the Graphics2D
class. This class is a subclass of the Graphics
class. If you use a version of the JDK that is Java 2D enabled, methods such as paintComponent
automatically receive an object of the Graphics2D
class. Simply use a cast, as follows:
public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; . . . }
The Java 2D library organizes geometric shapes in an object-oriented fashion. In particular, there are classes to represent lines, rectangles, and ellipses:
Line2D Rectangle2D Ellipse2D
These classes all implement the Shape
interface.
The Java 2D library supports more complex shapes—in particular, arcs, quadratic and cubic curves, and general paths. See Volume 2 for more information.
To draw a shape, you first create an object of a class that implements the Shape
interface and then call the draw
method of the Graphics2D
class. For example,
Rectangle2D rect = . . .; g2.draw(rect);
Before the Java 2D library appeared, programmers used methods of the Graphics
class such as drawRectangle
to draw shapes. Superficially, the old-style method calls look a bit simpler. However, by using the Java 2D library, you keep your options open—you can later enhance your drawings with some of the many tools that the Java 2D library supplies.
Using the Java 2D shape classes introduces some complexity. Unlike the 1.0 draw methods, which used integer pixel coordinates, the Java 2D shapes use floating-point coordinates. In many cases, that is a great convenience because it allows you to specify your shapes in coordinates that are meaningful to you (such as millimeters or inches) and then translate to pixels. The Java 2D library uses single-precision float
quantities for many of its internal floating-point calculations. Single precision is sufficient—after all, the ultimate purpose of the geometric computations is to set pixels on the screen or printer. As long as any roundoff errors stay within one pixel, the visual outcome is not affected. Furthermore, float
computations are faster on some platforms, and float
values require half the storage of double
values.
However, manipulating float
values is sometimes inconvenient for the programmer because the Java programming language is adamant about requiring casts when converting double
values into float
values. For example, consider the following statement:
float f = 1.2; // Error
This statement does not compile because the constant 1.2
has type double
, and the compiler is nervous about loss of precision. The remedy is to add an F
suffix to the floating-point constant:
float f = 1.2F; // Ok
Now consider this statement:
Rectangle2D r = . . . float f = r.getWidth(); // Error
This statement does not compile either, for the same reason. The getWidth
method returns a double
. This time, the remedy is to provide a cast:
float f = (float) r.getWidth(); // Ok
Because the suffixes and casts are a bit of a pain, the designers of the 2D library decided to supply two versions of each shape class: one with float
coordinates for frugal programmers, and one with double
coordinates for the lazy ones. (In this book, we fall into the second camp and use double
coordinates whenever we can.)
The library designers chose a curious, and initially confusing, method for packaging these choices. Consider the Rectangle2D
class. This is an abstract class with two concrete subclasses, which are also static inner classes:
Rectangle2D.Float Rectangle2D.Double
Figure 7–8 shows the inheritance diagram.
It is best to try to ignore the fact that the two concrete classes are static inner classes—that is just a gimmick to avoid names such as FloatRectangle2D
and DoubleRectangle2D
. (For more information on static inner classes, see Chapter 6.)
When you construct a Rectangle2D.Float
object, you supply the coordinates as float
numbers. For a Rectangle2D.Double
object, you supply them as double
numbers.
Rectangle2D.Float floatRect = new Rectangle2D.Float(10.0F, 25.0F, 22.5F, 20.0F); Rectangle2D.Double doubleRect = new Rectangle2D.Double(10.0, 25.0, 22.5, 20.0);
Actually, because both Rectangle2D.Float
and Rectangle2D.Double
extend the common Rectangle2D
class and the methods in the subclasses simply override methods in the Rectangle2D
superclass, there is no benefit in remembering the exact shape type. You can simply use Rectangle2D
variables to hold the rectangle references.
Rectangle2D floatRect = new Rectangle2D.Float(10.0F, 25.0F, 22.5F, 20.0F); Rectangle2D doubleRect = new Rectangle2D.Double(10.0, 25.0, 22.5, 20.0);
That is, you only need to use the pesky inner classes when you construct the shape objects.
The construction parameters denote the top-left corner, width, and height of the rectangle.
Actually, the Rectangle2D.Float
class has one additional method that is not inherited from Rectangle2D
, namely, setRect(float x, float y, float h, float w)
. You lose that method if you store the Rectangle2D.Float
reference in a Rectangle2D
variable. But it is not a big loss—the Rectangle2D
class has a setRect
method with double
parameters.
The Rectangle2D
methods use double
parameters and return values. For example, the getWidth
method returns a double
value, even if the width is stored as a float
in a Rectangle2D.Float
object.
Simply use the Double
shape classes to avoid dealing with float
values altogether. However, if you are constructing thousands of shape objects, then you can consider using the Float
classes to conserve memory.
What we just discussed for the Rectangle2D
classes holds for the other shape classes as well. Furthermore, there is a Point2D
class with subclasses Point2D.Float
and Point2D.Double
. Here is how to make a point object.
Point2D p = new Point2D.Double(10, 20);
The Point2D
class is very useful—it is more object oriented to work with Point2D
objects than with separate x- and y- values. Many constructors and methods accept Point2D
parameters. We suggest that you use Point2D
objects when you can—they usually make geometric computations easier to understand.
The classes Rectangle2D
and Ellipse2D
both inherit from the common superclass RectangularShape
. Admittedly, ellipses are not rectangular, but they have a bounding rectangle (see Figure 7–9).
The RectangularShape
class defines over 20 methods that are common to these shapes, among them such useful methods as getWidth
, getHeight
, getCenterX
, and getCenterY
(but sadly, at the time of this writing, not a getCenter
method that returns the center as a Point2D
object).
Finally, a couple of legacy classes from JDK 1.0 have been fitted into the shape class hierarchy. The Rectangle
and Point
classes, which store a rectangle and a point with integer coordinates, extend the Rectangle2D
and Point2D
classes.
Figure 7–10 shows the relationships between the shape classes. However, the Double
and Float
subclasses are omitted. Legacy classes are marked with a gray fill.
Rectangle2D
and Ellipse2D
objects are simple to construct. You need to specify
The x- and y-coordinates of the top-left corner; and
The width and height.
For ellipses, these refer to the bounding rectangle.
For example,
Ellipse2D e = new Ellipse2D.Double(150, 200, 100, 50);
constructs an ellipse that is bounded by a rectangle with the top-left corner at (150, 200), width 100, and height 50.
However, sometimes you don’t have the top-left corner readily available. It is quite common to have two diagonal corner points of a rectangle, but perhaps they aren’t the top-left and bottom-right corners. You can’t simply construct a rectangle as
Rectangle2D rect = new Rectangle2D.Double(px, py, qx - px, qy - py); // Error
If p
isn’t the top-left corner, one or both of the coordinate differences will be negative and the rectangle will come out empty. In that case, first create a blank rectangle and use the setFrameFromDiagonal
method.
Rectangle2D rect = new Rectangle2D.Double(); rect.setFrameFromDiagonal(px, py, qx, qy);
Or, even better, if you know the corner points as Point2D
objects p
and q
,
rect.setFrameFromDiagonal(p, q);
When constructing an ellipse, you usually know the center, width, and height, and not the corner points of the bounding rectangle (which don’t even lie on the ellipse). The setFrameFromCenter
method uses the center point, but it still requires one of the four corner points. Thus, you will usually end up constructing an ellipse as follows:
Ellipse2D ellipse = new Ellipse2D.Double(centerX - width / 2, centerY - height / 2, width, height);
To construct a line, you supply the start and end points, either as Point2D
objects or as pairs of numbers:
Line2D line = new Line2D.Double(start, end);
or
Line2D line = new Line2D.Double(startX, startY, endX, endY);
The program in Example 7–4 draws
Figure 7–11 shows the result.
Example 7–4. DrawTest.java
1. import java.awt.*; 2. import java.awt.geom.*; 3. import javax.swing.*; 4. 5. public class DrawTest 6. { 7. public static void main(String[] args) 8. { 9. DrawFrame frame = new DrawFrame(); 10. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 11. frame.setVisible(true); 12. } 13. } 14. 15. /** 16. A frame that contains a panel with drawings 17. */ 18. class DrawFrame extends JFrame 19. { 20. public DrawFrame() 21. { 22. setTitle("DrawTest"); 23. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 24. 25. // add panel to frame 26. 27. DrawPanel panel = new DrawPanel(); 28. add(panel); 29. } 30. 31. public static final int DEFAULT_WIDTH = 400; 32. public static final int DEFAULT_HEIGHT = 400; 33. } 34. 35. /** 36. A panel that displays rectangles and ellipses. 37. */ 38. class DrawPanel extends JPanel 39. { 40. public void paintComponent(Graphics g) 41. { 42. super.paintComponent(g); 43. Graphics2D g2 = (Graphics2D) g; 44. 45. // draw a rectangle 46. 47. double leftX = 100; 48. double topY = 100; 49. double width = 200; 50. double height = 150; 51. 52. Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height); 53. g2.draw(rect); 54. 55. // draw the enclosed ellipse 56. 57. Ellipse2D ellipse = new Ellipse2D.Double(); 58. ellipse.setFrame(rect); 59. g2.draw(ellipse); 60. 61. // draw a diagonal line 62. 63. g2.draw(new Line2D.Double(leftX, topY, leftX + width, topY + height)); 64. 65. // draw a circle with the same center 66. 67. double centerX = rect.getCenterX(); 68. double centerY = rect.getCenterY(); 69. double radius = 150; 70. 71. Ellipse2D circle = new Ellipse2D.Double(); 72. circle.setFrameFromCenter(centerX, centerY, centerX + radius, centerY + radius); 73. g2.draw(circle); 74. } 75. }
double getCenterX()
double getCenterY()
double getMinX()
double getMinY()
double getMaxX()
double getMaxY()
return the center, minimum, or maximum x- or y-value of the enclosing rectangle.
double getWidth()
double getHeight()
return the width or height of the enclosing rectangle.
double getX()
double getY()
return the x- or y-coordinate of the top-left corner of the enclosing rectangle.
java.awt.geom.Rectangle2D.Double 1.2
Rectangle2D.Double(double x, double y, double w, double h)
constructs a rectangle with the given top-left corner, width, and height.
java.awt.geom.Rectangle2D.Float 1.2
Rectangle2D.Float(float x, float y, float w, float h)
constructs a rectangle with the given top-left corner, width, and height.
Ellipse2D.Double(double x, double y, double w, double h)
constructs an ellipse whose bounding rectangle has the given top-left corner, width, and height.
Point2D.Double(double x, double y)
constructs a point with the given coordinates.
Line2D.Double(Point2D start, Point2D end)
Line2D.Double(double startX, double startY, double endX, double endY)
construct a line with the given start and end points.
The setPaint
method of the Graphics2D
class lets you select a color that is used for all subsequent drawing operations on the graphics context. To draw in multiple colors, you select a color, draw, then select another color, and draw again.
You define colors with the Color
class. The java.awt.Color
class offers predefined constants for the 13 standard colors listed in Table 7–1.
For example,
g2.setPaint(Color.RED); g2.drawString("Warning!", 100, 100);
Before JDK 1.4, color constant names were lower case, such as Color.
red
. This is odd because the standard coding convention is to write constants in upper case. Starting with JDK 1.4, you can write the standard color names in upper case or, for backward compatibility, in lower case.
You can specify a custom color by creating a Color
object by its red, green, and blue components. Using a scale of 0–255 (that is, one byte) for the redness, blueness, and greenness, call the Color
constructor like this:
Color(int redness, int greenness, int blueness)
Here is an example of setting a custom color:
g2.setPaint(new Color(0, 128, 128)); // a dull blue-green g2.drawString("Welcome!", 75, 125);
In addition to solid colors, you can select more complex “paint” settings, such as varying hues or images. See the Advanced AWT chapter in Volume 2 for more details. If you use a Graphics
object instead of a Graphics2D
object, you need to use the setColor
method to set colors.
To set the background color, you use the setBackground
method of the Component
class, an ancestor of JPanel
.
MyPanel p = new MyPanel(); p.setBackground(Color.PINK);
There is also a setForeground
method. It specifies the default color that is used for drawing on the component.
The brighter()
and darker()
methods of the Color
class produce, as their names suggest, either brighter or darker versions of the current color. Using the brighter
method is also a good way to highlight an item. Actually, brighter()
is just a little bit brighter. To make a color really stand out, apply it three times: c.brighter().brighter().brighter()
.
Java gives you predefined names for many more colors in its SystemColor
class. The constants in this class encapsulate the colors used for various elements of the user’s system. For example,
p.setBackground(SystemColor.window)
sets the background color of the panel to the default used by all windows on the user’s desktop. (The background is filled in whenever the window is repainted.) Using the colors in the SystemColor
class is particularly useful when you want to draw user interface elements so that the colors match those already found on the user’s desktop. Table 7–2 lists the system color names and their meanings.
Color(int r, int g, int b)
creates a color object.
Table 7–2. System Colors
| Background color of desktop |
| Background color for captions |
| Text color for captions |
| Border color for caption text |
| Background color for inactive captions |
| Text color for inactive captions |
| Border color for inactive captions |
| Background for windows |
| Color of window border frame |
| Text color inside windows |
| Background for menus |
| Text color for menus |
| Background color for text |
| Text color for text |
| Text color for inactive controls |
| Background color for highlighted text |
| Text color for highlighted text |
| Background color for controls |
| Text color for controls |
| Light highlight color for controls |
| Highlight color for controls |
| Shadow color for controls |
| Dark shadow color for controls |
| Background color for scrollbars |
| Background color for spot-help text |
| Text color for spot-help text |
Parameters: |
| The red value (0–255) |
| The green value (0–255) | |
| The blue value (0–255) |
void setColor(Color c)
changes the current color. All subsequent graphics operations will use the new color.
Parameters: |
| The new color |
java.awt.Graphics2D 1.2
void setPaint(Paint p)
sets the paint attribute of this graphics context. The Color
class implements the Paint
interface. Therefore, you can use this method to set the paint attribute to a solid color.
void setBackground(Color c)
sets the background color.
Parameters: | c | The new background color |
void setForeground(Color c)
sets the foreground color.
Parameters: |
| The new foreground color |
You can fill the interiors of closed shapes (such as rectangles or ellipses) with a color (or, more generally, the current paint setting). Simply call fill
instead of draw
:
Rectangle2D rect = . . .; g2.setPaint(Color.RED); g2.fill(rect); // fills rect with red color
The program in Example 7–5 fills a rectangle in red, then an ellipse with the same boundary in a dull green (see Figure 7–12).
Example 7–5. FillTest.java
1. import java.awt.*; 2. import java.awt.geom.*; 3. import javax.swing.*; 4. 5. public class FillTest 6. { 7. public static void main(String[] args) 8. { 9. FillFrame frame = new FillFrame(); 10. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 11. frame.setVisible(true); 12. } 13. } 14. 15. /** 16. A frame that contains a panel with drawings 17. */ 18. class FillFrame extends JFrame 19. { 20. public FillFrame() 21. { 22. setTitle("FillTest"); 23. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 24. 25. // add panel to frame 26. 27. FillPanel panel = new FillPanel(); 28. add(panel); 29. } 30. 31. public static final int DEFAULT_WIDTH = 400; 32. public static final int DEFAULT_HEIGHT = 400; 33. } 34. 35. /** 36. A panel that displays filled rectangles and ellipses 37. */ 38. class FillPanel extends JPanel 39. { 40. public void paintComponent(Graphics g) 41. { 42. super.paintComponent(g); 43. Graphics2D g2 = (Graphics2D) g; 44. 45. // draw a rectangle 46. 47. double leftX = 100; 48. double topY = 100; 49. double width = 200; 50. double height = 150; 51. 52. Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height); 53. g2.setPaint(Color.RED); 54. g2.fill(rect); 55. 56. // draw the enclosed ellipse 57. 58. Ellipse2D ellipse = new Ellipse2D.Double(); 59. ellipse.setFrame(rect); 60. g2.setPaint(new Color(0, 128, 128)); // a dull blue-green 61. g2.fill(ellipse); 62. } 63. }
The “Not a Hello, World” program at the beginning of this chapter displayed a string in the default font. Often, you want to show text in a different font. You specify a font by its font face name. A font face name is composed of a font family name, such as “Helvetica,” and an optional suffix such as “Bold.” For example, the font faces “Helvetica” and “Helvetica Bold” are both considered to be part of the family named “Helvetica.”
To find out which fonts are available on a particular computer, call the getAvailableFontFamilyNames
method of the GraphicsEnvironment
class. The method returns an array of strings that contains the names of all available fonts. To obtain an instance of the GraphicsEnvironment
class that describes the graphics environment of the user’s system, use the static getLocalGraphicsEnvironment
method. Thus, the following program prints the names of all fonts on your system:
import java.awt.*; public class ListFonts { public static void main(String[] args) { String[] fontNames = GraphicsEnvironment .getLocalGraphicsEnvironment() .getAvailableFontFamilyNames(); for (String fontName : fontNames) System.out.println(fontName); } }
On one system, the list starts out like this:
Abadi MT Condensed Light Arial Arial Black Arial Narrow Arioso Baskerville Binner Gothic . . .
and goes on for another 70 fonts or so.
The JDK documentation claims that suffixes such as “heavy,” “medium,” “oblique,” or “gothic” are variations inside a single family. In our experience, that is not the case. The “Bold,” “Italic,” and “Bold Italic” suffixes are recognized as family variations, but other suffixes aren’t.
Unfortunately, there is no absolute way of knowing whether a user has a font with a particular “look” installed. Font face names can be trademarked, and font designs can be copyrighted in some jurisdictions. Thus, the distribution of fonts often involves royalty payments to a font foundry. Of course, just as there are inexpensive imitations of famous perfumes, there are lookalikes for name-brand fonts. For example, the Helvetica imitation that is shipped with Windows is called Arial.
To establish a common baseline, the AWT defines five logical font names:
SansSerif Serif Monospaced Dialog DialogInput
These names are always mapped to fonts that actually exist on the client machine. For example, on a Windows system, SansSerif is mapped to Arial.
The font mapping is defined in the fontconfig.properties
file in the jre/lib
subdirectory of the Java installation. See http://java.sun.com/j2se/5.0/docs/guide/intl/fontconfig.html for information on this file. Earlier versions of the JDK used a font.properties
file that is now obsolete.
To draw characters in a font, you must first create an object of the class Font
. You specify the font face name, the font style, and the point size. Here is an example of how you construct a Font
object:
Font helvb14 = new Font("Helvetica", Font.BOLD, 14);
The third argument is the point size. Points are commonly used in typography to indicate the size of a font. There are 72 points per inch. This sentence uses a 9-point font.
You can use a logical font name in the place of a font face name in the Font
constructor. You specify the style (plain, bold, italic, or bold italic) by setting the second Font
constructor argument to one of the following values:
Font.PLAIN Font.BOLD Font.ITALIC Font.BOLD + Font.ITALIC
Here is an example:
Font sansbold14 = new Font("SansSerif", Font.BOLD, 14)
Prior versions of Java used the names Helvetica, TimesRoman, Courier, and ZapfDingbats as logical font names. For backward compatibility, these font names are still treated as logical font names even though Helvetica is really a font face name and TimesRoman and ZapfDingbats are not font names at all—the actual font face names are “Times Roman” and “Zapf Dingbats.”
Starting with JDK version 1.3, you can read TrueType fonts. You need an input stream for the font—typically from a disk file or URL. (See Chapter 12 for more information on streams.) Then call the static Font.createFont
method:
URL url = new URL("http://www.fonts.com/Wingbats.ttf"); InputStream in = url.openStream(); Font f = Font.createFont(Font.TRUETYPE_FONT, in);
The font is plain with a font size of 1 point. Use the deriveFont
method to get a font of the desired size:
Font df = f.deriveFont(14.0F);
There are two overloaded versions of the deriveFont
method. One of them (with a float
parameter) sets the font size, the other (with an int
parameter) sets the font style. Thus, f.deriveFont(14)
sets the style and not the size! (The result is an italic font because it happens that the binary representation of 14 sets the ITALIC
bit but not the BOLD
bit.)
The Java fonts contain the usual ASCII characters as well as symbols. For example, if you print the character 'u2297'
in the Dialog font, then you get a ⊗ character. Only those symbols that are defined in the Unicode character set are available.
Here’s the code that displays the string “Hello, World!” in the standard sans serif font on your system, using 14-point bold type:
Font sansbold14 = new Font("SansSerif", Font.BOLD, 14); g2.setFont(sansbold14); String message = "Hello, World!"; g2.drawString(message, 75, 100);
Next, let’s center the string in its panel rather than drawing it at an arbitrary position. We need to know the width and height of the string in pixels. These dimensions depend on three factors:
The font used (in our case, sans serif, bold, 14 point);
The string (in our case, “Hello, World!”); and
The device on which the font is drawn (in our case, the user’s screen).
To obtain an object that represents the font characteristics of the screen device, you call the getFontRenderContext
method of the Graphics2D
class. It returns an object of the FontRenderContext
class. You simply pass that object to the getStringBounds
method of the Font
class:
FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context);
The getStringBounds
method returns a rectangle that encloses the string.
To interpret the dimensions of that rectangle, you should know some basic typesetting terms (see Figure 7–13). The baseline is the imaginary line where, for example, the bottom of a character like “e” rests. The ascent is the distance from the baseline to the top of an ascender, which is the upper part of a letter like “b” or “k,” or an uppercase character. The descent is the distance from the baseline to a descender, which is the lower portion of a letter like “p” or “g.”
Leading is the space between the descent of one line and the ascent of the next line. (The term has its origin from the strips of lead that typesetters used to separate lines.) The height of a font is the distance between successive baselines, which is the same as descent + leading + ascent.
The width of the rectangle that the getStringBounds
method returns is the horizontal extent of the string. The height of the rectangle is the sum of ascent, descent, and leading. The rectangle has its origin at the baseline of the string. The top y-coordinate of the rectangle is negative. Thus, you can obtain string width, height, and ascent as follows:
double stringWidth = bounds.getWidth(); double stringHeight = bounds.getHeight(); double ascent = -bounds.getY();
If you need to know the descent or leading, you need to use the getLineMetrics
method of the Font
class. That method returns an object of the LineMetrics
class, which has methods to obtain the descent and leading:
LineMetrics metrics = f.getLineMetrics(message, context); float descent = metrics.getDescent(); float leading = metrics.getLeading();
The following code uses all this information to center a string in its surrounding panel:
FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context); // (x,y) = top left corner of text double x = (getWidth() - bounds.getWidth()) / 2; double y = (getHeight() - bounds.getHeight()) / 2; // add ascent to y to reach the baseline double ascent = -bounds.getY(); double baseY = y + ascent; g2.drawString(message, (int) x, (int) baseY);
To understand the centering, consider that getWidth()
returns the width of the panel. A portion of that width, namely, bounds.getWidth()
, is occupied by the message string. The remainder should be equally distributed on both sides. Therefore, the blank space on each side is half the difference. The same reasoning applies to the height.
Finally, the program draws the baseline and the bounding rectangle.
Figure 7–14 shows the screen display; Example 7–6 is the program listing.
Example 7–6. FontTest.java
1. import java.awt.*; 2. import java.awt.font.*; 3. import java.awt.geom.*; 4. import javax.swing.*; 5. 6. public class FontTest 7. { 8. public static void main(String[] args) 9. { 10. FontFrame frame = new FontFrame(); 11. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 12. frame.setVisible(true); 13. } 14. } 15. 16. /** 17. A frame with a text message panel 18. */ 19. class FontFrame extends JFrame 20. { 21. public FontFrame() 22. { 23. setTitle("FontTest"); 24. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 25. 26. // add panel to frame 27. 28. FontPanel panel = new FontPanel(); 29. add(panel); 30. } 31. 32. public static final int DEFAULT_WIDTH = 300; 33. public static final int DEFAULT_HEIGHT = 200; 34. } 35. 36. /** 37. A panel that shows a centered message in a box. 38. */ 39. class FontPanel extends JPanel 40. { 41. public void paintComponent(Graphics g) 42. { 43. super.paintComponent(g); 44. Graphics2D g2 = (Graphics2D) g; 45. 46. String message = "Hello, World!"; 47. 48. Font f = new Font("Serif", Font.BOLD, 36); 49. g2.setFont(f); 50. 51. // measure the size of the message 52. 53. FontRenderContext context = g2.getFontRenderContext(); 54. Rectangle2D bounds = f.getStringBounds(message, context); 55. 56. // set (x,y) = top left corner of text 57. 58. double x = (getWidth() - bounds.getWidth()) / 2; 59. double y = (getHeight() - bounds.getHeight()) / 2; 60. 61. // add ascent to y to reach the baseline 62. 63. double ascent = -bounds.getY(); 64. double baseY = y + ascent; 65. 66. // draw the message 67. 68. g2.drawString(message, (int) x, (int) baseY); 69. 70. g2.setPaint(Color.GRAY); 71. 72. // draw the baseline 73. 74. g2.draw(new Line2D.Double(x, baseY, x + bounds.getWidth(), baseY)); 75. 76. // draw the enclosing rectangle 77. 78. Rectangle2D rect = new Rectangle2D.Double(x, y, bounds.getWidth(), bounds .getHeight()); 79. g2.draw(rect); 80. } 81. }
Font(String name, int style, int size)
creates a new font object.
Parameters: |
| The font name. This is either a font face name (such as “Helvetica Bold”) or a logical font name (such as “Serif”, “SansSerif”) |
| The style (Font.PLAIN, Font.BOLD, Font.ITALIC, or Font.BOLD + Font.ITALIC) | |
| The point size (for example, 12) |
String getFontName()
gets the font face name (such as “Helvetica Bold”).
String getFamily()
gets the font family name (such as “Helvetica”).
String getName()
gets the logical name (such as “SansSerif”) if the font was created with a logical font name; otherwise, gets the font face name.
Rectangle2D getStringBounds(String s, FontRenderContext context)
1.2
returns a rectangle that encloses the string. The origin of the rectangle falls on the baseline. The top y-coordinate of the rectangle equals the negative of the ascent. The height of the rectangle equals the sum of ascent, descent, and leading. The width equals the string width.
LineMetrics getLineMetrics(String s, FontRenderContext context)
1.2
returns a line metrics object to determine the extent of the string.
Font deriveFont(int style)
1.2
Font deriveFont(float size)
1.2
Font deriveFont(int style, float size)
1.2
return a new font that equals this font, except that it has the given size and style.
float getAscent()
gets the font ascent—the distance from the baseline to the tops of uppercase characters.
float getDescent()
gets the font descent—the distance from the baseline to the bottoms of descenders.
float getLeading()
gets the font leading—the space between the bottom of one line of text and the top of the next line.
float getHeight()
gets the total height of the font—the distance between the two baselines of text (descent + leading + ascent).
void setFont(Font font)
selects a font for the graphics context. That font will be used for subsequent text-drawing operations.
Parameters: |
| A font |
void drawString(String str, int x, int y)
draws a string in the current font and color.
Parameters: |
| The string to be drawn |
| The x-coordinate of the start of the string | |
| The y-coordinate of the baseline of the string |
FontRenderContext getFontRenderContext()
gets a font render context that specifies font characteristics in this graphics context.
void drawString(String str, float x, float y)
draws a string in the current font and color.
Parameters: |
| The string to be drawn |
| The x-coordinate of the start of the string | |
| The y-coordinate of the baseline of the string |
You have already seen how to build up simple images by drawing lines and shapes. Complex images, such as photographs, are usually generated externally, for example, with a scanner or special image-manipulation software. (As you will see in Volume 2, it is also possible to produce an image, pixel by pixel, and store the result in an array. This procedure is common for fractal images, for example.)
Once images are stored in local files or someplace on the Internet, you can read them into a Java application and display them on Graphics
objects. As of JDK 1.4, reading an image is very simple. If the image is stored in a local file, call
String filename = "..."; Image image = ImageIO.read(new File(filename));
Otherwise, you can supply a URL:
String urlname = "..."; Image image = ImageIO.read(new URL(urlname));
The read
method throws an IOException
if the image is not available. We discuss the general topic of exception handling in Chapter 11. For now, our sample program just catches that exception and prints a stack trace if it occurs.
Now the variable image
contains a reference to an object that encapsulates the image data. You can display the image with the drawImage
method of the Graphics
class.
public void paintComponent(Graphics g) { . . . g.drawImage(image, x, y, null); }
Example 7–7 takes this a little bit further and tiles the window with the graphics image. The result looks like the screen shown in Figure 7–15. We do the tiling in the paintComponent
method. We first draw one copy of the image in the top-left corner and then use the copyArea
call to copy it into the entire window:
for (int i = 0; i * imageWidth <= getWidth(); i++) for (int j = 0; j * imageHeight <= getHeight(); j++) if (i + j > 0) g.copyArea(0, 0, imageWidth, imageHeight, i * imageWidth, j * imageHeight);
To load an image with JDK 1.3 or earlier, you should use the MediaTracker
class instead. A media tracker can track the acquisition of one or more images. (The name “media” suggests that the class should also be able to track audio files or other media. While such an extension may have been envisioned for the future, the current implementation tracks images only.)
You add an image to a tracker object with the following command:
MediaTracker tracker = new MediaTracker(component); Image img = Toolkit.getDefaultToolkit().getImage(name); int id = 1; // the ID used to track the image loading process tracker.addImage(img, id);
You can add as many images as you like to a single media tracker. Each of the images should have a different ID number, but you can choose any numbering that is convenient. To wait for an image to be loaded completely, you use code like this:
try { tracker.waitForID(id); } catch (InterruptedException e) {}
If you want to acquire multiple images, then you can add them all to the media tracker object and wait until they are all loaded. You can achieve this with the following code:
try { tracker.waitForAll(); } catch (InterruptedException e) {}
Example 7–7 shows the full source code of the image display program. This concludes our introduction to Java graphics programming. For more advanced techniques, you can turn to the discussion about 2D graphics and image manipulation in Volume 2.
Example 7–7. ImageTest.java
1. import java.awt.*; 2. import java.awt.event.*; 3. import java.io.*; 4. import javax.imageio.*; 5. import javax.swing.*; 6. 7. public class ImageTest 8. { 9. public static void main(String[] args) 10. { 11. ImageFrame frame = new ImageFrame(); 12. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 13. frame.setVisible(true); 14. } 15. } 16. 17. /** 18. A frame with an image panel 19. */ 20. class ImageFrame extends JFrame 21. { 22. public ImageFrame() 23. { 24. setTitle("ImageTest"); 25. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 26. 27. // add panel to frame 28. 29. ImagePanel panel = new ImagePanel(); 30. add(panel); 31. } 32. 33. public static final int DEFAULT_WIDTH = 300; 34. public static final int DEFAULT_HEIGHT = 200; 35. } 36. 37. /** 38. A panel that displays a tiled image 39. */ 40. class ImagePanel extends JPanel 41. { 42. public ImagePanel() 43. { 44. // acquire the image 45. try 46. { 47. image = ImageIO.read(new File("blue-ball.gif")); 48. } 49. catch (IOException e) 50. { 51. e.printStackTrace(); 52. } 53. } 54. 55. public void paintComponent(Graphics g) 56. { 57. super.paintComponent(g); 58. if (image == null) return; 59. 60. int imageWidth = image.getWidth(this); 61. int imageHeight = image.getHeight(this); 62. 63. // draw the image in the upper-left corner 64. 65. g.drawImage(image, 0, 0, null); 66. // tile the image across the panel 67. 68. for (int i = 0; i * imageWidth <= getWidth(); i++) 69. for (int j = 0; j * imageHeight <= getHeight(); j++) 70. if (i + j > 0) 71. g.copyArea(0, 0, imageWidth, imageHeight, 72. i * imageWidth, j * imageHeight); 73. } 74. 75. private Image image; 76. }
static BufferedImage read(File f)
static BufferedImage read(URL u)
read an image from the given file or URL.
java.awt.Image 1.0
Graphics getGraphics()
gets a graphics context to draw into this image buffer.
void flush()
releases all resources held by this image object.
boolean drawImage(Image img, int x, int y, ImageObserver observer)
draws an unscaled image. Note: This call may return before the image is drawn.
Parameters: |
| The image to be drawn |
| The x-coordinate of the upper-left corner | |
| The y-coordinate of the upper-left corner | |
| The object to notify of the progress of the rendering process (may be |
boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
draws a scaled image. The system scales the image to fit into a region with the given width and height. Note: This call may return before the image is drawn.
Parameters: |
| The image to be drawn |
| The x-coordinate of the upper-left corner | |
| The y-coordinate of the upper-left corner | |
| The desired width of image | |
| The desired height of image | |
| The object to notify of the progress of the rendering process (may be null) |
void copyArea(int x, int y, int width, int height, int dx, int dy)
copies an area of the screen.
Parameters: |
| The x-coordinate of the upper-left corner of the source area |
| The y-coordinate of the upper-left corner of the source area | |
| The width of the source area | |
| The height of the source area | |
| The horizontal distance from the source area to the target area | |
| The vertical distance from the source area to the target area |
void dispose()
disposes of this graphics context and releases operating system resources. You should always dispose of the graphics contexts that you receive from calls to methods such as Image.getGraphics
, but not the ones handed to you by paintComponent
.
MediaTracker(Component c)
tracks images that are displayed in the given component.
void addImage(Image image, int id)
adds an image to the list of images being tracked. When the image is added, the image loading process is started.
Parameters: |
| The image to be tracked |
| The identifier used to later refer to this image |
void waitForID(int id)
waits until all images with the specified ID are loaded.
void waitForAll()
waits until all images that are being tracked are loaded.
[1] For what are apparently copyright reasons, the Windows and Macintosh look and feel are available only with the Java runtime environments for those platforms.