10.4 Creating Drawings

10.4 Creating Drawings How would you modify the InvestmentFrame3 program if you didn’t want to use scroll bars? 16. Practice It 487 Now you can t...
Author: Rudolf Randall
16 downloads 0 Views 474KB Size
10.4 Creating Drawings

How would you modify the InvestmentFrame3 program if you didn’t want to use scroll bars?

16.

Practice It

487

Now you can try these exercises at the end of the chapter: R10.13, P10.9, P10.10.

10.4 Creating Drawings You often want to include simple drawings such as graphs or charts in your programs. The Java library does not have any standard components for this purpose, but it is fairly easy to make your own drawings. The following sections show how.

10.4.1 Drawing on a Component

In order to display a drawing, provide a class that extends the JComponent class.

We start out with a simple bar chart (see Figure 8) that is composed of three rectangles. You cannot draw directly onto a frame. Instead, you add a component to the frame and draw on the component. To do so, extend the JComponent class and override its paintComponent method.

You can make simple drawings out of lines, rectangles, and circles.

public class ChartComponent extends JComponent { public void paintComponent(Graphics g) {

Drawing instructions } } Place drawing instructions inside the paintComponent method. That method is called whenever the component needs to be repainted.

When the component is shown for the first time, its paintComponent method is called automatically. The method is also called when the window is resized, or when it is shown again after it was hidden. The paintComponent method receives an object of type Graphics. The Graphics object stores the graphics state—the current color, font, and so on, that are used for drawing operations. The Graphics class has methods for drawing geometric shapes. The call g.fillRect(x, y, width, height)

draws a solid rectangle with upper-left corner (x, y) and the given width and height.

Figure 8

Drawing a Bar Chart

488 Chapter 10 Graphical User Interfaces

The Graphics class has methods to draw rectangles and other shapes.

Here we draw three rectangles. They line up on the left because they all have x = 0. They also all have the same height. public class ChartComponent extends JComponent { public void paintComponent(Graphics g) { g.fillRect(0, 10, 200, 10); g.fillRect(0, 30, 300, 10); g.fillRect(0, 50, 100, 10); } }

Note that the coordinate system is different from the one used in mathematics. The origin (0, 0) is at the upper-left corner of the component, and the y-coordinate grows downward. (0, 0)

x

(20, 10)

(10, 20)

y

Here is the source code for the ChartComponent class. As you can see from the import statements, the Graphics class is part of the java.awt package. section_4_1/ChartComponent.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

import java.awt.Graphics; import javax.swing.JComponent; /**

A component that draws a bar chart. */ public class ChartComponent extends JComponent { public void paintComponent(Graphics g) { g.fillRect(0, 10, 200, 10); g.fillRect(0, 30, 300, 10); g.fillRect(0, 50, 100, 10); } }

Now we need to add the component to a frame, and show the frame. Because the frame is so simple, we don’t make a frame subclass. Here is the viewer class: section_4_1/ChartViewer.java 1 2

import javax.swing.JComponent; import javax.swing.JFrame;

10.4 Creating Drawings 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

489

public class ChartViewer { public static void main(String[] args) { JFrame frame = new JFrame(); frame.setSize(400, 200); frame.setTitle("A bar chart"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JComponent component = new ChartComponent(); frame.add(component); frame.setVisible(true); } }

10.4.2 Ovals, Lines, Text, and Color In the preceding section, you learned how to write a program that draws rectangles. Now we turn to additional graphical elements that allow you to draw quite a few interesting pictures. To draw an oval, you specify its bounding box (see Figure 9) in the same way that you would specify a rectangle, namely by the x- and y-coordinates of the top-left corner and the width and height of the box. Then the call g.drawOval(x, y, width, height);

draws the outline of an oval. To draw a circle, simply set the width and height to the same values: g.drawOval(x, y, diameter, diameter);

(x, y)

Width

Height

Use drawRect, drawOval, and drawLine to draw geometric shapes.

Notice that (x, y) is the top-left corner of the bounding box, not the center of the circle. If you want to fill the inside of an oval, use the fillOval method instead. Conversely, if you want only the outline of a rectangle, with no filling, use the drawRect method.

Figure 9

An Oval and Its Bounding Box

490 Chapter 10 Graphical User Interfaces Figure 10

Baseline

Basepoint and Baseline Basepoint

To draw a line, call the drawLine method with the x- and y-coordinates of both end points: g.drawLine(x1, y1, x2, y2); The drawString method draws a string, starting at its basepoint.

You often want to put text inside a drawing, for example, to label some of the parts. Use the drawString method of the Graphics class to draw a string anywhere in a window. You must specify the string and the x- and y-coordinates of the basepoint of the first character in the string (see Figure 10). For example, g.drawString("Message", 50, 100);

When you first start drawing, all shapes and strings are drawn with a black pen. To change the color, you need to supply an object of type Color. Java uses the RGB color model. That is, you specify a color by the amounts of the primary colors—red, green, and blue—that make up the color. The amounts are given as integers between 0 (primary color not present) and 255 (maximum amount present). For example, Color magenta = new Color(255, 0, 255);

constructs a Color object with maximum red, no green, and maximum blue, yielding a bright purple color called magenta.

Table 1 Predefined Colors Color

RGB Values

Color.BLACK

0, 0, 0

Color.BLUE

0, 0, 255

Color.CYAN

0, 255, 255

Color.GRAY

128, 128, 128

Color.DARKGRAY

64, 64, 64

Color.LIGHTGRAY

192, 192, 192

Color.GREEN

0, 255, 0

Color.MAGENTA

255, 0, 255

Color.ORANGE

255, 200, 0

Color.PINK

255, 175, 175

Color.RED

255, 0, 0

Color.WHITE

255, 255, 255

Color.YELLOW

255, 255, 0

10.4 Creating Drawings

When you set a new color in the graphics context, it is used for subsequent drawing operations.

491

For your convenience, a variety of colors have been predefined in the Color class. Table 1 shows those predefined colors and their RGB values. For example, Color.PINK has been predefined to be the same color as new Color(255, 175, 175). To draw a shape in a different color, first set the color of the Graphics object, then call the drawing method: g.setColor(Color.YELLOW); g.fillOval(350, 25, 35, 20); // Fills the oval in yellow

The following program puts all these shapes to work, creating a simple chart (see Figure 11.

Figure 11

A Bar Chart with a Label

section_4_2/ChartComponent2.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

import java.awt.Color; import java.awt.Graphics; import javax.swing.JComponent; /**

A component that draws a demo chart. */ public class ChartComponent2 extends JComponent { public void paintComponent(Graphics g) { // Draw the bars g.fillRect(0, 10, 200, 10); g.fillRect(0, 30, 300, 10); g.fillRect(0, 50, 100, 10); // Draw the arrow g.drawLine(350, 35, 305, 35); g.drawLine(305, 35, 310, 30); g.drawLine(305, 35, 310, 40); // Draw the highlight and the text g.setColor(Color.YELLOW); g.fillOval(350, 25, 35, 20); g.setColor(Color.BLACK); g.drawString("Best", 355, 40); } }

492 Chapter 10 Graphical User Interfaces section_4_2/ChartViewer2.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

import javax.swing.JComponent; import javax.swing.JFrame; public class ChartViewer2 { public static void main(String[] args) { JFrame frame = new JFrame(); frame.setSize(400, 200); frame.setTitle("A bar chart"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JComponent component = new ChartComponent2(); frame.add(component); frame.setVisible(true); } }

10.4.3 Application: Visualizing the Growth of an Investment In this section, we will add a bar chart to the investment program of Section 10.3. Whenever the user clicks on the "Add Interest" button, another bar is added to the bar chart (see Figure 12). The chart class of the preceding section produced a fixed bar chart. We will develop an improved version that can draw a chart with any values. The chart keeps an array list of the values: public class ChartComponent extends JComponent { private ArrayList values; private double maxValue; . . . }

When drawing the bars, we need to scale the values to fit into the chart. For example, if the investment program adds a value such as 10050 to the chart, we don’t want to draw a bar that is 10,050 pixels long. In order to scale the values, we need to know the largest value that should still fit inside the chart. We will ask the user of the chart component to provide that maximum in the constructor: public ChartComponent(double max) { values = new ArrayList(); maxValue = max; }

We compute the width of a bar as int barWidth = (int) (getWidth() * value / maxValue);

The getWidth method returns the width of the component in pixels. If the value to be drawn equals maxValue, the bar stretches across the entire component width.

10.4 Creating Drawings

493

Figure 12 Clicking on the “Add Interest” Button Adds a Bar to the Chart

Here is the complete paintComponent method. We stack the bars horizontally and leave small gaps between them: public void paintComponent(Graphics g) { final int GAP = 5; final int BAR_HEIGHT = 10; int y = GAP; for (double value : values) { int barWidth = (int) (getWidth() * value / maxValue); g.fillRect(0, y, barWidth, BAR_HEIGHT); y = y + BAR_HEIGHT + GAP; } }

Whenever the user clicks the “Add Interest” button, a value is added to the array list. Afterward, it is essential to call the repaint method: public void append(double value) { values.add(value); repaint(); }

Call the repaint method whenever the state of a painted component changes.

The call to repaint forces a call to the paintComponent method. The paintComponent method redraws the component. Then the graph is drawn again, now showing the appended value. Why not call paintComponent directly? The simple answer is that you can’t—you don’t have a Graphics object that you can pass as an argument. Instead, you need to ask the Swing library to make the call to paintComponent at its earliest convenience. That is what the repaint method does.

494 Chapter 10 Graphical User Interfaces

When placing a painted component into a panel, you need to specify its preferred size.

We need to address another issue with painted components. If you place a painted component into a panel, you need to specify its preferred size. Otherwise, the panel will assume that the preferred size is 0 by 0 pixels, and you won’t be able to see the component. Specifying the preferred size of a painted component is conceptually similar to specifying the number of rows and columns in a text area. Call the setPreferredSize method with a Dimension object as argument. A Dimension argument wraps a width and a height into a single object. The call has the form chart.setPreferredSize(new Dimension(CHART_WIDTH, CHART_HEIGHT));

That’s all that is required to add a diagram to an application. Here is the code for the chart and frame classes; the viewer class is with the book’s companion code. section_4_3/ChartComponent.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

import import import import

java.awt.Color; java.awt.Graphics; java.util.ArrayList; javax.swing.JComponent;

/**

A component that draws a chart. */ public class ChartComponent extends JComponent { private ArrayList values; private double maxValue; public ChartComponent(double max) { values = new ArrayList(); maxValue = max; } public void append(double value) { values.add(value); repaint(); } public void paintComponent(Graphics g) { final int GAP = 5; final int BAR_HEIGHT = 10; int y = GAP; for (double value : values) { int barWidth = (int) (getWidth() * value / maxValue); g.fillRect(0, y, barWidth, BAR_HEIGHT); y = y + BAR_HEIGHT + GAP; } } }

section_4_3/InvestmentFrame4.java 1 2

import java.awt.Dimension; import java.awt.event.ActionEvent;

10.4 Creating Drawings 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

import import import import import import

java.awt.event.ActionListener; javax.swing.JButton; javax.swing.JFrame; javax.swing.JLabel; javax.swing.JPanel; javax.swing.JTextField;

/**

A frame that shows the growth of an investment with variable interest, using a bar chart. */ public class InvestmentFrame4 extends JFrame { private static final int FRAME_WIDTH = 400; private static final int FRAME_HEIGHT = 400; private static final int CHART_WIDTH = 300; private static final int CHART_HEIGHT = 300; private static final double DEFAULT_RATE = 5; private static final double INITIAL_BALANCE = 1000; private private private private private

JLabel rateLabel; JTextField rateField; JButton button; ChartComponent chart; double balance;

public InvestmentFrame4() { balance = INITIAL_BALANCE; chart = new ChartComponent(3 * INITIAL_BALANCE); chart.setPreferredSize(new Dimension(CHART_WIDTH, CHART_HEIGHT)); chart.append(INITIAL_BALANCE); createTextField(); createButton(); createPanel(); setSize(FRAME_WIDTH, FRAME_HEIGHT); } private void createTextField() { rateLabel = new JLabel("Interest Rate: "); final int FIELD_WIDTH = 10; rateField = new JTextField(FIELD_WIDTH); rateField.setText("" + DEFAULT_RATE); } class AddInterestListener implements ActionListener { public void actionPerformed(ActionEvent event) { double rate = Double.parseDouble(rateField.getText()); double interest = balance * rate / 100; balance = balance + interest; chart.append(balance); }

495

496 Chapter 10 Graphical User Interfaces 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

SELF CHECK

17. 18. 19. 20. 21. 22. 23. 24.

} private void createButton() { button = new JButton("Add Interest"); ActionListener listener = new AddInterestListener(); button.addActionListener(listener); } private void createPanel() { JPanel panel = new JPanel(); panel.add(rateLabel); panel.add(rateField); panel.add(button); panel.add(chart); add(panel); } }

How do you modify the program in Section 10.4.1 to draw two squares? What happens if you call fillOval instead fillRect in the program of Section 10.4.1? Give instructions to draw a circle with center (100, 100) and radius 25. Give instructions to draw a letter “V” by drawing two line segments. Give instructions to draw a string consisting of the letter “V”. What are the RGB color values of Color.BLUE? How do you draw a yellow square on a red background? What would happen in the investment viewer program if we simply painted each bar as g.fillRect(0, y, value, BAR_HEIGHT);

25. 26.

Practice It

Common Error 10.3

in the paintComponent method of the ChartComponent class? What would happen if you omitted the call to repaint in the append method of the ChartComponent class? What would happen if you omitted the call to chart.setPreferredSize in the InvestmentFrame4 constructor?

Now you can do: R10.18, P10.17, P10.18.

Forgetting to Repaint When you change the data in a painted component, the component is not automatically painted with the new data. You must call the repaint method of the component. Your component’s paintComponent method will then be invoked. Note that you should not call the paintComponent method directly.

10.4 Creating Drawings

497

The best place to call repaint is in the method of your component that modifies the data values: void changeData(. . .) {

Update data values repaint(); }

This is a concern only for your own painted components. When you make a change to a standard Swing component such as a JLabel, the component is automatically repainted.

Common Error 10.4

By Default, Components Have Zero Width and Height You must be careful when you add a painted component, such as a component displaying a chart, to a panel. The default size for a JComponent is 0 by 0 pixels, and the component will not be visible. The remedy is to call the setPreferredSize method: chart.setPreferredSize(new Dimension(CHART_WIDTH, CHART_HEIGHT));

This is an issue only for painted components. Buttons, labels, and so on, know how to compute their preferred size.

HOW T O 10 .1

Drawing Graphical Shapes Suppose you want to write a program that displays graphical shapes such as cars, aliens, charts, or any other images that can be obtained from rectangles, lines, and ellipses. These instructions give you a step-by-step procedure for decomposing a drawing into parts and implementing a program that produces the drawing. In this How To we will create a program to draw a national flag.

Step 1

Determine the shapes that you need for the drawing. You can use the following shapes: • Squares and rectangles • Circles and ellipses • Lines The outlines of these shapes can be drawn in any color, and you can fill the insides of these shapes with any color. You can also use text to label parts of your drawing. Some national flag designs consist of three equally wide sections of different colors, side by side, as in the Italian flag shown below.

498 Chapter 10 Graphical User Interfaces You could draw such a flag using three rectangles. But if the middle rectangle is white, as it is, for example, in the flag of Italy (green, white, red), it is easier and looks better to draw a line on the top and bottom of the middle portion: Two lines

Two rectangles

Step 2

Find the coordinates for the shapes. You now need to find the exact positions for the geometric shapes. • For rectangles, you need the x- and y-position of the top-left corner, the width, and the height. • For ellipses, you need the top-left corner, width, and height of the bounding rectangle. • For lines, you need the x- and y-positions of the starting point and the end point. • For text, you need the x- and y-position of the basepoint. A commonly-used size for a window is 300 by 300 pixels. You may not want the flag crammed all the way to the top, so perhaps the upper-left corner of the flag should be at point (100, 100). Many flags, such as the flag of Italy, have a width : height ratio of 3 : 2. (You can often find exact proportions for a particular flag by doing a bit of Internet research on one of several Flags of the World sites.) For example, if you make the flag 90 pixels wide, then it should be 60 pixels tall. (Why not make it 100 pixels wide? Then the height would be 100 · 2 / 3 5 67, which seems more awkward.) Now you can compute the coordinates of all the important points of the shape:

Step 3

(100, 100)

(130, 100)

(160, 100)

(190, 100)

(100, 160)

(130, 160)

(160, 160)

(190, 160)

Write Java statements to draw the shapes. In our example, there are two rectangles and two lines: g.setColor(Color.GREEN); g.fillRect(100, 100, 30, 60); g.setColor(Color.RED); g.fillRect(160, 100, 30, 60);

10.4 Creating Drawings

499

g.setColor(Color.BLACK); g.drawLine(130, 100, 160, 100); g.drawLine(130, 160, 160, 160);

If you are more ambitious, then you can express the coordinates in terms of a few variables. In the case of the flag, we have arbitrarily chosen the top-left corner and the width. All other coordinates follow from those choices. If you decide to follow the ambitious approach, then the rectangles and lines are determined as follows: g.fillRect(xLeft, yTop, width / 3, width * 2 / 3); . . . g.fillRect(xLeft + 2 * width / 3, yTop, width / 3, width * 2 / 3); . . . g.drawLine(xLeft + width / 3, yTop, xLeft + width * 2 / 3, yTop); g.drawLine(xLeft + width / 3, yTop + width * 2 / 3, xLeft + width * 2 / 3, yTop + width * 2 / 3);

Step 4

Consider using methods or classes for repetitive steps. Do you need to draw more than one flag? Perhaps with different sizes? Then it is a good idea to design a method or class, so you won’t have to repeat the same drawing instructions. For example, you can write a method void drawItalianFlag(Graphics g, int xLeft, int yTop, int width) {

Draw a flag at the given location and size }

Place the instructions from the preceding step into this method. Then you can call drawItalianFlag(g, 10, 10, 100); drawItalianFlag(g, 10, 125, 150);

in the paintComponent method to draw two flags. Step 5

Place the drawing instructions in the paintComponent method. public class ItalianFlagComponent extends JComponent { public void paintComponent(Graphics g) {

Drawing instructions } }

If your drawing is simple, simply place all drawing statements here. Otherwise, call the methods you created in Step 4. Step 6

Write the viewer class. Provide a viewer class, with a main method in which you construct a frame, add your component, and make your frame visible. The viewer class is completely routine; you only need to change a single line to show a different component. public class ItalianFlagViewer { public static void main(String[] args) { JFrame frame = new JFrame(); frame.setSize(300, 400); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JComponent component = new ItalianFlagComponent(); frame.add(component);

500 Chapter 10 Graphical User Interfaces frame.setVisible(true);

ONLINE EXAMPLE

The complete flag drawing program.

} }

W ORK E D EX AMPL E 1 0 .1

Coding a Bar Chart Creator

In this Worked Example, we will develop a simple program for creating bar charts. The user enters labels and values for the bars, and the program displays the chart.

VI DEO EX AMP L E 1 0 .1

Solving Crossword Puzzles

In this Video Example, we develop a program that finds words for solving a crossword puzzle.

CHAPTER SUMMARY Display frames and add components inside frames.

• To show a frame, construct a JFrame object, set its size, and make it visible. • Use a JPanel to group multiple user-interface components together. • Declare a JFrame subclass for a complex frame. Explain the event concept and handle button events.

• User-interface events include key presses, mouse moves, button clicks, menu selections, and so on. • An event listener belongs to a class created by the application programmer. Its methods describe the actions to be taken when an event occurs. • Event sources report on events. When an event occurs, the event source notifies all event listeners. • Attach an ActionListener to each button so that your program can react to button clicks. • Methods of an inner class can access variables from the surrounding class. Use text components for reading text input.

• Use a JTextField component for reading a single line of input. Place a JLabel next to each text field. • Use a JTextArea to show multiple lines of text. • You can add scroll bars to any component with a JScrollPane. Available online in WileyPLUS and at www.wiley.com/college/horstmann.