Solution for
Programming Exercise 6.4
THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to
the following exercise from this on-line
Java textbook.
Exercise 6.4:
In Exercise 3.5, you drew a checkerboard.
For this exercise, write a checkerboard applet where the user can select a square
by clicking on it. Hilite the selected square by drawing a colored border around it.
When the applet is first created, no square is selected. When the user clicks on
a square that is not currently selected, it becomes selected. If the user clicks the
square that is selected, it becomes unselected. Assume that the size of the
applet is 160 by 160 pixels, so that each square on the checkerboard is 20 by
20 pixels. Here is a working version of the applet:
Discussion
See the solution to Exercise 3.5
for a discussion of how to draw the checkerboard. In that exercise,
the code for drawing the board was in the paint() method of a plain Applet.
However, the same code works in the paintComponent()
method that I wrote for the solution to this exercise.
As always, there are many ways to organize the applet. In this
case, I decided to put all the programming into a drawing surface
class named Board. Besides this nested class, there is nothing
in the main applet class but an init() method. The init()
method simply creates a Board and uses if for the content pane
of the applet. This can be done in a single line:
setContentPane( new Board() );
The rest of this discussion concerns the Board class.
This drawing surface class is a subclass of JPanel and
will also be used to respond to mouse clicks, so its definition
begins
class Board extends JPanel implements MouseListener {
To keep track of which square is selected, if any, the Board class contains
instance variables, selectedRow and selectedCol.
When no square is selected, selectedRow is -1 (and I don't care
what selectedCol is). When a square is selected,
selectedRow is the number of the row that contains that
square and selectedCol is the number of the column that
contains the selected square. Remember that rows and columns are
numbered from 0 to 7. This makes some of the calculations easier
than numbering them from 1 to 8.
The paintComponent() method has to hilite the selected square.
I do this by drawing a cyan border around the inside of the selected
square. This is the new code that is added to the paint()
method from Exercise 3.5:
if (selectedRow >= 0) {
// Since there is a selected square, draw a cyan
// border around it.
g.setColor(Color.cyan);
y = selectedRow * 20;
x = selectedCol * 20;
g.drawRect(x, y, 19, 19);
g.drawRect(x+1, y+1, 17, 17);
}
Since the squares are 20 pixels on each side, you might wonder
why the first drawRect() command specifies a width and
height of 19 instead of 20. In the fillRect() method that
is used earlier in the paint() method to fill in the square,
a width and height of 20 is used. Remember that the drawRect()
method actually draws a rectangle whose width and height are
one more than the values specified in the parameters. (Remember the
bit about the pen that hangs one pixel outside the rectangle?)
To respond to user mouse clicks, the board must implement the
MouseListener interface. The constructor in the Board class
calls addMouseListener(this) to register the
board to listen for mouse events on itself. (Remember that
calling addMouseListener(this) is the same as calling
this.addMouseListener(this).) Of the five methods
specified in the MouseListener interface, only
mousePressed has a non-empty definition. This method
must figure out which square the user clicked and adjust the
values of the instance variables selectedRow and
selectedCol accordingly.
Let's say that the user clicked at the point (x,y).
The problem is to determine which square on the checkerboard
contains that point. The column number of the square is obtained
by dividing the x coordinate by the width of the
squares. Since the squares are 20 pixels wide, the
row number of the clicked square is x/20. For values
of x between 0 and 19, this gives a column number of 0,
which is correct. For the next 20 pixels, from 20 to 39, x/20
is 1, which is the correct column number. For the next strip of
pixels, from 40 to 59, the answer is 2. And so on. Similarly,
y/20 gives the row number of the square where the user
clicked.
Once we know the row and column where the user clicked, we
can compare them to selectedRow and selectedCol.
If the values are the same, then the user clicked in a square that was
already selected. We want to remove the hiliting. That can be
done by setting selectedRow = -1, the value that indicates
that no square is selected. Otherwise, the values of selectedRow
and selectedCol are set to the row and column that the user
clicked.
All this explains the reasoning behind the mousePressed()
routine, which you can see below.
The Solution
/*
This applet draws a red-and-black checkerboard.
It is assumed that the size of the applet is 160
by 160 pixels. When the user clicks a square, that
square is selected, unless it is already selected.
When the user clicks the selected square, it is
unselected. If there is a selected square, it is
hilited with a cyan border.
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ClickableCheckerboard extends JApplet{
public void init() {
// This does nothing but make an object belonging
// to the nested class, Board, and add set it
// to be the content pane of the applet.
setContentPane( new Board() );
}
class Board extends JPanel implements MouseListener {
// This nested class represents the drawing surface
// of the applet, and also contains all the other
// programming for the applet.
int selectedRow; // Row and column of selected square. If no
int selectedCol; // square is selected, selectedRow is -1.
Board() {
// Constructor. Set selectedRow to -1 to indicate that
// no square is selected. And set the board object
// to listen for mouse events on itself.
selectedRow = -1;
addMouseListener(this);
}
public void paintComponent(Graphics g) {
// Draw the checkerboard and hilite selected square, if any.
// (Note: paintComponent() is not necessary, since this
// method already paints the entire surface of the object.
// This assumes that the object is exactly 160-by-160 pixels.
int row; // Row number, from 0 to 7
int col; // Column number, from 0 to 7
int x,y; // Top-left corner of square
for ( row = 0; row < 8; row++ ) {
for ( col = 0; col < 8; col++) {
x = col * 20;
y = row * 20;
if ( (row % 2) == (col % 2) )
g.setColor(Color.red);
else
g.setColor(Color.black);
g.fillRect(x, y, 20, 20);
}
} // end for row
if (selectedRow >= 0) {
// Since there is a selected square, draw a cyan
// border around it. (If selectedRow < 0, then
// no square is selected and no border is drawn.)
g.setColor(Color.cyan);
y = selectedRow * 20;
x = selectedCol * 20;
g.drawRect(x, y, 19, 19);
g.drawRect(x+1, y+1, 17, 17);
}
} // end paint()
public void mousePressed(MouseEvent evt) {
// When the user clicks on the applet, figure out which
// row and column the click was in and change the
// selected square accordingly.
int col = evt.getX() / 20; // Column where user clicked.
int row = evt.getY() / 20; // Row where user clicked.
if (selectedRow == row && selectedCol == col) {
// User clicked on the currently selected square.
// Turn off the selection by setting selectedRow to -1.
selectedRow = -1;
}
else {
// Change the selection to the square the user clicked on.
selectedRow = row;
selectedCol = col;
}
repaint();
} // end mouseDown()
public void mouseReleased(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
} // end nested class Board
} // end class ClickableCheckerboard
[ Exercises
| Chapter Index
| Main Index
]