Solution for
Programming Exercise 6.5
THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to
the following exercise from this on-line
Java textbook.
Exercise 6.5:
Write an applet that shows two squares. The user should be able to drag
either square with the mouse. (You'll need an instance variable to remember
which square the user is dragging.) The user can drag the square off the applet
if she wants; if she does this, it's gone. You can try it here:
Discussion
To write this applet, you need to understand dragging, as discussed
in Section 6.4. To support dragging, you have
to implement both the MouseListener and MouseMotionListener
interfaces and register some object to listen for both mouse and
mouse motion events. The code for dragging a square
is spread out over three methods, mousePressed,
mouseReleased, and mouseDragged. Several instance
variables are needed to keep track of what is going on while a
dragging operation is being executed. A general framework for dragging
is given in Section 6.4. This example is simplified a bit because
while dragging the square, we only need to know the current position of
the mouse so that we can move the square to that position. We don't need
to keep track of the previous position of the mouse.
As always for any implementation of dragging, I use a boolean variable,
dragging, to keep track of whether or not a drag operation is in progress.
Not every mouse press starts a drag operation. If the use clicks the
applet outside of the squares, there is nothing to drag. Since there are
two squares to be dragged, we have to keep track of which is being dragged.
I use a boolean variable, dragRedSquare, which is true if the red
square is being dragged and is false if the blue square is being dragged.
(A boolean variable is actually not the best choice in this case. It would
be a problem if we wanted to add another square. A boolean variable only
has two possible values, so an integer variable would probably be a better
choice.) I keep track of the locations of the squares with integer
instance variables x1 and y1 for the upper left
corner of the red square and x2 and y2 for the upper
left corner of the blue square.
There is one little problem. The mouse location is a single
(x,y) point. A square occupies a whole bunch of points.
When we move the square to follow the mouse, where exactly should
we put the square? One possibility is to put the upper left corner
of the square at the mouse location. If we did this, the mouseDragged
routine would look like:
public void mouseDragged(MouseEvent evt) {
if (dragging == false)
return;
int x = evt.getX(); // Get mouse position.
int y = evt.getY();
if (dragRedSquare) { // Move the red square.
x1 = x; // Put top-left corner at mouse position.
y1 = y;
}
else { // Move the blue square.
x2 = x; // Put top-left corner at mouse position.
y2 = y;
}
repaint();
}
This works, but it not very aesthetic. When the user starts dragging
a square, no matter where in the square the user clicks, the square will
jump so that its top-left corner is at the mouse position.
This is not what a user typically expects. If I grab a square by
clicking its center, then I want the center to stay under the mouse
cursor as I move it. If I grab the lower right corner, I want
the lower right corner to follow the mouse, not the upper left
corner. There is a solution to this, and it's one that is often
needed for dragging operations. We need to record the original position of the
mouse relative to the upper left corner of the square. This tells us
where in the square the user clicked. This is done in the mousePressed
routine by assigning appropriate values to
instance variables offsetX and offsetY:
if (x >= x2 && x < x2+30 && y >= y2 && y < y2+30) {
// It's the blue square (which should be checked first,
// since it's in front of the red square.)
dragging = true;
dragRedSquare = false;
offsetX = x - x2; // Distance from corner of square to (x,y).
offsetY = y - y2;
}
else if (x >= x1 && x < x1+30 && y >= y1 && y < y1+30) {
// It's the red square.
dragging = true;
dragRedSquare = true;
offsetX = x - x1; // Distance from corner of square to (x,y).
offsetY = y - y1;
}
In mouseDragged, when the mouse moves to a new (x,y)
point, we move the square so that the vertical and horizontal distances
between the mouse location and the top left corner of the square
remain the same:
if (dragRedSquare) { // Move the red square.
x1 = x - offsetX; // Offset corner from mouse location.
y1 = y - offsetY;
}
else { // Move the blue square.
x2 = x - offsetX; // Offset corner from mouse location.
y2 = y - offsetY;
}
There is, as usual, the question of how to divide the responsibilities
of the program between the main applet class and the nested class that
represents the drawing surface. In this case, I used a very simple
anonymous nested class for the drawing surface. You will find this
class in the applet's init() method. The only method in the
anonymous class is the paintComponent() method that does the drawing.
All this leads to the complete source code, shown below.
By the way, if you wanted to stop the user from dragging the
square outside the applet, you would just have to add code to
the mouseDragged routine to "clamp"
the variables x1, y1, x2, and y2
so that they lie in the acceptable range. Here is a modified
routine that keeps the square entirely within the applet:
public void mouseDragged(MouseEvent evt) {
if (dragging == false)
return;
int x = evt.getX();
int y = evt.getY();
if (dragRedSquare) { // Move the red square.
x1 = x - offsetX;
y1 = y - offsetY;
if (x1 < 0) // Clamp (x1,y1) so the square lies in the applet.
x1 = 0;
else if (x1 >= getSize().width - 30)
x1 = getSize().width - 30;
if (y1 < 0)
y1 = 0;
else if (y1 >= getSize().height - 30)
y1 = getSize().height - 30;
}
else { // Move the blue square.
x2 = x - offsetX;
y2 = y - offsetY;
if (x2 < 0) // Clamp (x2,y2) so the square lies in the applet.
x2 = 0;
else if (x2 >= getSize().width - 30)
x2 = getSize().width - 30;
if (y2 < 0)
y2 = 0;
else if (y2 >= getSize().height - 30)
y2 = getSize().height - 30;
}
repaint();
}
The Solution
/*
An applet showing a red square and a blue square that the user
can drag with the mouse. The user can drag the squares off
the applet and drop them. There is no way of getting them back.
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DragTwoSquares extends JApplet
implements MouseListener, MouseMotionListener {
int x1, y1; // Coords of top-left corner of the red square.
int x2, y2; // Coords of top-left corner of the blue square.
/* Some variables used during dragging */
boolean dragging; // Set to true when a drag is in progress.
boolean dragRedSquare; // True if red square is being dragged, false
// if blue square is being dragged.
int offsetX, offsetY; // Offset of mouse-click coordinates from
// top-left corner of the square that was
// clicked.
JPanel drawSurface; // This is the panel on which the actual
// drawing is done. It is used as the
// content pane of the applet. It actually
// belongs to an anonymous class which is
// defined in place in the init() method.
public void init() {
// Initialize the applet by putting the squares in a
// starting position and creating the drawing surface
// and installing it as the content pane of the applet.
x1 = 10; // Set up initial positions of the squares.
y1 = 10;
x2 = 50;
y2 = 10;
drawSurface = new JPanel() {
// This anonymous inner class defines the drawing
// surface for the applet.
public void paintComponent(Graphics g) {
// Draw the two squares and a black frame
// around the panel.
super.paintComponent(g); // Fill with background color.
g.setColor(Color.red);
g.fillRect(x1, y1, 30, 30);
g.setColor(Color.blue);
g.fillRect(x2, y2, 30, 30);
g.setColor(Color.black);
g.drawRect(0,0,getSize().width-1,getSize().height-1);
}
};
drawSurface.setBackground(Color.lightGray);
drawSurface.addMouseListener(this);
drawSurface.addMouseMotionListener(this);
setContentPane(drawSurface);
} // end init();
public void mousePressed(MouseEvent evt) {
// Respond when the user presses the mouse on the panel.
// Check which square the user clicked, if any, and start
// dragging that square.
if (dragging) // Exit if a drag is already in progress.
return;
int x = evt.getX(); // Location where user clicked.
int y = evt.getY();
if (x >= x2 && x < x2+30 && y >= y2 && y < y2+30) {
// It's the blue square (which should be checked first,
// since it's in front of the red square.)
dragging = true;
dragRedSquare = false;
offsetX = x - x2; // Distance from corner of square to (x,y).
offsetY = y - y2;
}
else if (x >= x1 && x < x1+30 && y >= y1 && y < y1+30) {
// It's the red square.
dragging = true;
dragRedSquare = true;
offsetX = x - x1; // Distance from corner of square to (x,y).
offsetY = y - y1;
}
}
public void mouseReleased(MouseEvent evt) {
// Dragging stops when user releases the mouse button.
dragging = false;
}
public void mouseDragged(MouseEvent evt) {
// Respond when the user drags the mouse. If a square is
// not being dragged, then exit. Otherwise, change the position
// of the square that is being dragged to match the position
// of the mouse. Note that the corner of the square is placed
// in the same position with respect to the mouse that it had
// when the user started dragging it.
if (dragging == false)
return;
int x = evt.getX();
int y = evt.getY();
if (dragRedSquare) { // Move the red square.
x1 = x - offsetX;
y1 = y - offsetY;
}
else { // Move the blue square.
x2 = x - offsetX;
y2 = y - offsetY;
}
drawSurface.repaint();
}
public void mouseMoved(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
} // end class DragTwoSquares
[ Exercises
| Chapter Index
| Main Index
]