Chapter 7. Graphics Programming

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.

Introducing Swing

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.

Note

Note

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.

The Windows look and feel of SwingSwingWindows look and feel

Figure 7–1. The Windows look and feel of Swing

The Motif look and feel of SwingSwingMotifMotif look and feel

Figure 7–2. The Motif look and feel of Swing

Note

Note

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.

The Ocean theme of the Metal look and feelMetalJava look and feel

Figure 7–3. The Ocean theme of the Metal look and feel

Note

Note

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.

Creating a Frame

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.

Caution

Caution

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. }
The simplest visible frame

Figure 7–4. The simplest visible frame

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.

Note

Note

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.

Note

Note

As of JDK 1.4, you can turn off all frame decorations by calling frame.setUndecorated(true).

Note

Note

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.

Positioning a Frame

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.

Inheritance hierarchy for the JFrame and JPanel classesJFrameinheritance hierarchyJPanelinheritance hierarchyJFrame and JPanelhierarchiesJFrame and JPanel inheritance hierarchy

Figure 7–5. Inheritance hierarchy for the JFrame and JPanel classes

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)

Note

Note

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.

Tip

Tip

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.

Tip

Tip

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);

Note

Note

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. }
CenteredFrameTest.javaframescentered frame exampleSwingcentered frame example
java.awt.Component 1.0
  • 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.

CenteredFrameTest.javaframescentered frame exampleSwingcentered frame example
java.awt.Window 1.0
  • 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.

CenteredFrameTest.javaframescentered frame exampleSwingcentered frame example
java.awt.Frame 1.0
  • 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:

    image

    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
    
CenteredFrameTest.javaframescentered frame exampleSwingcentered frame example
java.awt.Toolkit 1.0
  • 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.

Displaying Information in a Panel

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.

A simple graphical program

Figure 7–6. A simple graphical program

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);
Internal structure of a JFrameframesinternal structureSwingframesinternal structureJFrameinternal structure

Figure 7–7. Internal structure of a JFrame

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

  • Define a class that extends JPanel; and

  • Override the paintComponent method in that class.

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.

Note

Note

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.

Tip

Tip

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. }
NotHelloWorld.java
javax.swing.JFrame 1.2
  • 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.)

NotHelloWorld.java
java.awt.Component 1.0
  • 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.”

NotHelloWorld.java
javax.swing.JComponent 1.2
  • void paintComponent(Graphics g)

    override this method to describe how your component needs to be painted.

Working with 2D Shapes

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.

Note

Note

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);

Note

Note

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.

2D rectangle classesRectangle2DrectanglesRectangle2DSwingrectangles

Figure 7–8. 2D rectangle classes

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.

Note

Note

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.

Tip

Tip

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);

Tip

Tip

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 bounding rectangle of an ellipseellipsesbounding rectanglebounding rectanglerectanglesbounding rectangle

Figure 7–9. The bounding rectangle of an ellipse

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.

Relationships between the shape classes

Figure 7–10. Relationships between the shape classes

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,
Relationships between the shape classes 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

  • A rectangle;

  • The ellipse that is enclosed in the rectangle;

  • A diagonal of the rectangle; and

  • A circle that has the same center as the rectangle.

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. }
Rectangles and ellipses

Figure 7–11. Rectangles and ellipses

Rectangles and ellipses
java.awt.geom.RectangularShape 1.2
  • 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.

Rectangles and ellipses
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.

Rectangles and ellipses
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.

Rectangles and ellipses
java.awt.geom.Ellipse2D.Double 1.2
  • 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.

Rectangles and ellipses
java.awt.geom.Point2D.Double 1.2
  • Point2D.Double(double x, double y)

    constructs a point with the given coordinates.

Rectangles and ellipses
java.awt.geom.Line2D.Double 1.2
  • 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.

Using Color

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.

Table 7–1. Standard Colors

BLACK

GREEN

RED

BLUE

LIGHT_GRAY

WHITE

CYAN

MAGENTA

YELLOW

DARK_GRAY

ORANGE

 

GRAY

PINK

 

For example,

g2.setPaint(Color.RED);
g2.drawString("Warning!", 100, 100);

Note

Note

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);

Note

Note

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.

Tip

Tip

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

desktop

Background color of desktop

activeCaption

Background color for captions

activeCaptionText

Text color for captions

activeCaptionBorder

Border color for caption text

inactiveCaption

Background color for inactive captions

inactiveCaptionText

Text color for inactive captions

inactiveCaptionBorder

Border color for inactive captions

window

Background for windows

windowBorder

Color of window border frame

windowText

Text color inside windows

menu

Background for menus

menuText

Text color for menus

text

Background color for text

textText

Text color for text

textInactiveText

Text color for inactive controls

textHighlight

Background color for highlighted text

textHighlightText

Text color for highlighted text

control

Background color for controls

controlText

Text color for controls

controlLtHighlight

Light highlight color for controls

controlHighlight

Highlight color for controls

controlShadow

Shadow color for controls

controlDkShadow

Dark shadow color for controls

scrollbar

Background color for scrollbars

info

Background color for spot-help text

infoText

Text color for spot-help text

System Colors
java.awt.Color 1.0

Parameters:

r

The red value (0–255)

 

g

The green value (0–255)

 

b

The blue value (0–255)

System Colors
java.awt.Graphics 1.0
  • void setColor(Color c)

    changes the current color. All subsequent graphics operations will use the new color.

    Parameters:

    c

    The new color

System Colors
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.

System Colors
java.awt.Component 1.0
  • void setBackground(Color c)

    sets the background color.

    Parameters:

    c

    The new background color

  • void setForeground(Color c)

    sets the foreground color.

    Parameters:

    c

    The new foreground color

Filling Shapes

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. }
Filled rectangles and ellipses

Figure 7–12. Filled rectangles and ellipses

Using Special Fonts for Text

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.

Note

Note

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.

Note

Note

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)

Note

Note

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.”

Tip

Tip

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);

Caution

Caution

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.”

Typesetting terms illustratedtypesetting terminologytexttypesetting terminologyfontstypesetting terminologySwingtypesetting terminology

Figure 7–13. Typesetting terms illustrated

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
FontTest.java.getHeight());
79.       g2.draw(rect);
80.    }
81. }
Drawing the baseline and string boundsbaseline, drawingboundsdrawing string boundsbounding rectangle

Figure 7–14. Drawing the baseline and string bounds

Drawing the baseline and string boundsbaseline, drawingboundsdrawing string boundsbounding rectangle
java.awt.Font 1.0
  • Font(String name, int style, int size)

    creates a new font object.

    Parameters:

    name

    The font name. This is either a font face name (such as “Helvetica Bold”) or a logical font name (such as “Serif”, “SansSerif”)

     

    style

    The style (Font.PLAIN, Font.BOLD, Font.ITALIC, or Font.BOLD + Font.ITALIC)

     

    size

    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.

Drawing the baseline and string boundsbaseline, drawingboundsdrawing string boundsbounding rectangle
java.awt.font.LineMetrics 1.2
  • 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).

Drawing the baseline and string boundsbaseline, drawingboundsdrawing string boundsbounding rectangle
java.awt.Graphics 1.0
  • void setFont(Font font)

    selects a font for the graphics context. That font will be used for subsequent text-drawing operations.

    Parameters:

    font

    A font

  • void drawString(String str, int x, int y)

    draws a string in the current font and color.

    Parameters:

    str

    The string to be drawn

     

    x

    The x-coordinate of the start of the string

     

    y

    The y-coordinate of the baseline of the string

Drawing the baseline and string boundsbaseline, drawingboundsdrawing string boundsbounding rectangle
java.awt.Graphics2D 1.2
  • 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:

    str

    The string to be drawn

     

    x

    The x-coordinate of the start of the string

     

    y

    The y-coordinate of the baseline of the string

Doing More with Images

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);
Window with tiled graphics image

Figure 7–15. Window with tiled graphics image

Note

Note

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. }
ImageTest.java
javax.swing.ImageIO 1.4
  • static BufferedImage read(File f)

  • static BufferedImage read(URL u)

    read an image from the given file or URL.

ImageTest.java
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.

ImageTest.java
java.awt.Graphics 1.0
  • 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:

    img

    The image to be drawn

     

    x

    The x-coordinate of the upper-left corner

     

    y

    The y-coordinate of the upper-left corner

     

    observer

    The object to notify of the progress of the rendering process (may be null)

  • 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:

    img

    The image to be drawn

     

    x

    The x-coordinate of the upper-left corner

     

    y

    The y-coordinate of the upper-left corner

     

    width

    The desired width of image

     

    height

    The desired height of image

     

    observer

    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:

    x

    The x-coordinate of the upper-left corner of the source area

     

    y

    The y-coordinate of the upper-left corner of the source area

     

    width

    The width of the source area

     

    height

    The height of the source area

     

    dx

    The horizontal distance from the source area to the target area

     

    dy

    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.

ImageTest.java
java.awt.Component 1.0
  • Image createImage(int width, int height)

    creates an off-screen image buffer to be used for double buffering.

    Parameters:

    width

    The width of the image

     

    height

    The height of the image

ImageTest.java
java.awt.MediaTracker 1.0
  • 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:

    image

    The image to be tracked

     

    id

    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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset