Section 6.4
Mouse Events
EVENTS ARE CENTRAL to programming for
a graphical user interface. A GUI program doesn't have a
main() routine that outlines what will happen when
the program is run, in a step-by-step process from beginning
to end. Instead, the program must be prepared to respond to
various kinds of events that can happen at unpredictable
times and in an order that the program doesn't control. The most
basic kinds of events are generated by the mouse and keyboard.
The user can press any key on the keyboard, move the
mouse, or press a button on the mouse. The user can do any of
these things at any time, and the computer has to respond
appropriately.
In Java, events are represented by objects. When an event occurs,
the system collects all the information relevant to the event and
constructs an object to contain that information. Different types
of events are represented by objects belonging to different classes.
For example, when the user presses a button on the mouse,
an object belonging to a class called MouseEvent is
constructed. The object contains information such as the
GUI component on which the user clicked, the (x,y) coordinates
of the point in the component where the click occurred, and which button
on the mouse was pressed. When the user
presses a key on the keyboard, a KeyEvent is created.
After the event object is constructed, it is passed as a parameter
to a designated subroutine. By writing that subroutine, the
programmer says what should happen when the event occurs.
As a Java programmer, you get a fairly high-level view of events.
There is lot of processing that goes on between the time that the user
presses a key or moves the mouse and the time that a subroutine in your
program is called to respond to the event. Fortunately, you don't need
to know much about that processing. But you should understand this
much: Even though your GUI program doesn't have a main() routine,
there is a sort of main routine running somewhere that executes
a loop of the form
while the program is still running:
Wait for the next event to occur
Call a subroutine to handle the event
This loop is called an event loop. Every
GUI program has an event loop. In Java, you don't have to write the loop.
It's part of "the system". If you write a GUI program in
some other language, you might have to provide a main routine
that runs an event loop.
In this section, we'll look at handling mouse events in Java, and we'll
cover the framework for handling events in general. The next
section will cover keyboard events. Java also has other types of
events, which are produced by GUI components. These will be introduced
in Section 6 and covered in detail in
Section 7.3.
For an event to have any effect, a program must detect the event
and react to it. In order to detect an event, the program must
"listen" for it. Listening for events is something that
is done by an object called an event listener.
An event listener object must contain instance methods for handling
the events for which it listens. For example, if an object is to
serve as a listener for events of type MouseEvent,
then it must contain the following method (among several others):
public void mousePressed(MouseEvent evt) { . . . }
The body of the method defines how the object responds when
it is notified that a mouse button has been pressed. The parameter,
evt, contains information about the event. This information can
be used by the listener object to determine its response.
The methods that are required in a mouse event listener are specified in
an interface named MouseListener. To be used as a listener
for mouse events, an object must implement this MouseListener
interface. Java
interfaces were covered in Section 5.5.
(To review briefly: An interface in Java is just a list of instance methods.
A class can "implement" an interface by doing two things. First, the class must
be declared to implement the interface, as in "class MyListerner
implements MouseListener" or "class RandomStrings extends
Applet implements MouseListener". Second, the class must
include a definition for each instance method specified in the interface.
An interface can be used as the type for a variable or formal
parameter. We say that an object implements the MouseListener
interface if it belongs to a class that implements the MouseListener
interface. Note that it is not enough for the object to include the
specified methods. It must also belong to a class that is specifically declared
to implement the interface.)
Every event in Java is associated with a GUI component. For example, when the
user presses a button on the mouse, the associated component is the one that the user
clicked on. Before a
listener object can "hear" events associated with a given component,
the listener object must be registered with the component. If a
MouseListener object, mListener, needs to hear mouse events
associated with a component object, comp, the listener must be
registered with the component by calling "comp.addMouseListener(mListener);".
The addActionListener() method is an instance method in the class,
Component. In particular, since an applet is a component, every applet
has an addMouseListener(), and so it is possible to set up a listener
to respond to clicks on the applet.
The event classes, such as MouseEvent, and the listener
interfaces, such as MouseListener, are defined in the package
java.awt.event. This means that if you want to work with events,
you should include the line "import java.awt.event.*;"
at the beginning of your source code file.
Admittedly, there is a large number of details to tend to
when you want to use events. To summarize, you must
- Put the import specification "import java.awt.event.*;"
at the beginning of your source code;
- Declare that some class implements the appropriate listener interface,
such as MouseListener;
- Provide definitions in the class for the subroutines from that interface;
- Register the listener object with the applet or other component.
Any object can act as a listener, if it implements the appropriate interface.
It is considered good form to define
new classes just for listening. Unfortunately, doing this effectively
requires some rather advanced techniques. (See Section 7.6.)
We'll use another strategy, which works well for small projects:
Since an applet is itself an object, we'll let the applet itself listen
for events. This means that the applet class will be declared to implement
any necessary listener interfaces, and it will include the necessary methods
to respond to the events.
MouseEvent and MouseListener
The MouseListener interface specifies five different
instance methods:
public void mousePressed(MouseEvent evt);
public void mouseReleased(MouseEvent evt);
public void mouseClicked(MouseEvent evt);
public void mouseEntered(MouseEvent evt);
public void mouseExited(MouseEvent evt);
The mousePressed method is called as soon as the user presses
down on one of the mouse buttons, and mouseReleased is called
when the user releases a button. These are the two methods that are
most commonly used, but any mouse listener object must define
all five methods. You can leave the body of a method empty if
you don't want to define a response. The mouseClicked method is
called if the user presses a mouse button and then releases it quickly, without moving
the mouse. In most cases, you should define mousePressed
instead of mouseClicked. The other two methods are called
when the mouse cursor enters or leaves the component. If you wanted
the component to change appearance whenever the user moves the mouse
over the component, you could define these two methods.
As an example, let's look at an applet that does something when the
user clicks on it. Here's an improved version of the RandomStrings applet from the
end of the previous section. In this version,
the applet will redraw itself when you click on it:
For this version of the applet, we need to make four changes in the
source code. First, add the line "import java.awt.event.*;"
before the class definition. Second, declare that the applet class implements
the MouseListener interface by saying:
class RandomStrings extends Applet implements MouseListener { ...
Third, define the five methods of the MouseListener interface. Only
mousePressed will do anything, and all it has to do is call repaint()
to force the applet to be redrawn. The following methods are added to
the class definition:
public void mousePressed(MouseEvent evt) {
// When user presses the mouse, tell the system to
// call the applet's paint() method.
repaint();
}
// The following empty routines are required by the
// MouseListener interface:
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }
Fourth and finally, the applet must be registered to listen for mouse events. This should
be done when the applet is initialized, that is, in the applet's init() method.
This line should be added to the init() method:
addMouseListener(this);
This might need some explanation. We want to listen for mouse events on the
applet itself, so we call the applet's own addMouseListener method. The parameter
to this method is the object that will be doing the listening. In this case, the object
is, again, the applet itself. "this" is a special variable that refers to
the applet. (See Section 5.5.) So, we are telling
the applet to listen for mouse events on itself. All this means, effectively,
is that our mousePressed method will be called when the user clicks on
the applet.
We could make all these changes in the source code of the original RandomStrings
applet. However, since we are supposed to be doing object-oriented programming,
it might be instructive to write a subclass that contains the changes. This will
let us build on previous work and concentrate just on the modifications.
Here's the actual source code for the above applet. It uses "super",
another special variable from Section 5.5.
import java.awt.*;
import java.awt.event.*;
public class ClickableRandomStrings extends RandomStrings
implements MouseListener {
public void init() {
// When the applet is created, do the initialization
// of the superclass, RandomStrings. Then set this
// applet to listen for mouse events on itself.
super.init();
addMouseListener(this);
}
public void mousePressed(MouseEvent evt) {
// When user presses the mouse, tell the system to
// call the applet's paint() method.
repaint();
}
// The following empty routines are required by the
// MouseListener interface:
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }
} // end class ClickableRandomStrings
Often, when a mouse event occurs, you want to know the location of
the mouse cursor. This information is available from the parameter to
the event-handling method, evt.
This parameter is an object of type MouseEvent, and it
contains instance methods that return information about the event.
To find out the coordinates of
the mouse cursor, call evt.getX() and evt.getY(). These
methods return integers which give the x and y coordinates
where the mouse cursor was positioned. The coordinates are expressed
in the component's coordinate system, where the top left corner of the component
is (0,0).
The user can hold down certain modifier keys
while using the mouse. The possible modifier keys include: the Shift key,
the Control key, the ALT key (called the Option key on the Macintosh),
and the Meta key (called the Command or Apple key on the Macintosh and with no
equivalent in Windows). You might want to respond to a mouse event
differently when the user is holding down a modifier key. The boolean-valued
instance methods evt.isShiftDown(), evt.isControlDown(),
evt.isAltDown(), and evt.isMetaDown() can be called
to test whether the modifier keys are pressed.
You might also want to have different responses depending on whether
the user presses the left mouse button, the middle mouse button, or
the right mouse button. Now, not every mouse has
a middle button and a right button, so Java handles the information
in a peculiar way. It treats pressing the right button as equivalent
to holding down the Meta key. That is, if the right button is pressed,
then the instance method evt.isMetaDown() will return
true (even if the Meta key is not pressed). Similarly,
pressing the middle mouse button is equivalent to holding down the
ALT key. In practice, what this really means is that pressing
the right mouse button under Windows is equivalent to holding
down the Command key while pressing the mouse button on Macintosh.
A program tests for either of these by calling evt.isMetaDown().
As an example, consider the following applet. Click on the applet (with the
left mouse button) to place a red rectangle on the applet. Click with
the right mouse button (or hold down the Command key and click on a Macintosh)
to place a blue oval on the applet. Hold down the Shift key and click to
clear the applet. (I draw black outlines around the ovals and rectangles
so that they will look nice when they overlap.)
The source code for this applet follows. You can see how the
instance methods in the MouseEvent object are used.
You can also check for the Four Steps of Event
Handling ("import java.awt.event.*", "implements
MouseListener", "addMouseListener", and the
event-handling methods). This applet has no paint() method.
More properly speaking, it has the inherited paint() method
that doesn't draw anything. So, when the applet is repainted, it is
simply filled with the background color. We still have the problem that
we are not storing information about what is drawn on the applet.
So if the applet is covered up and uncovered, the contents of the
applet are erased.
You should pay attention to how the graphics context, g,
is used in the mousePressed routine. Since I am drawing on
the applet from outside its paint() method, I need to
obtain a graphics context by saying "g = getGraphics()".
I use g to draw an oval or rectangle on the applet, centered
on the point where the user clicked. Finally, the graphics context
is disposed by calling g.dispose().
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class SimpleStamper extends Applet implements MouseListener {
public void init() {
// When the applet is created, set its background color
// to black, and register the applet to listen to mouse
// events on itself.
setBackground(Color.black);
addMouseListener(this);
}
public void mousePressed(MouseEvent evt) {
// This method will be called when the user clicks the
// mouse on the applet.
if ( evt.isShiftDown() ) {
// The user was holding down the Shift key. Just
// repaint the applet, which will fill it with its
// background color, black.
repaint();
return;
}
int x = evt.getX(); // x-coordinate where user clicked.
int y = evt.getY(); // y-coordinate where user clicked.
Graphics g = getGraphics(); // Graphics context
// for drawing on the applet.
if ( evt.isMetaDown() ) {
// User right-clicked at the point (x,y).
// Draw a blue oval centered at the point (x,y).
// A black outline around the oval will make it more
// distinct when ovals and rects overlap.
g.setColor(Color.blue);
g.fillOval( x - 25, y - 15, 60, 30 );
g.setColor(Color.black);
g.drawOval( x - 25, y - 15, 60, 30 );
}
else {
// Draw a red rectangle centered at the point (x,y).
g.setColor(Color.red);
g.fillRect( x - 25, y - 15, 60, 30 );
g.setColor(Color.black);
g.drawRect( x - 25, y - 15, 60, 30 );
}
g.dispose(); // We are finished with the graphics context,
// so dispose of it.
} // end mousePressed()
// The following empty routines are required by the
// MouseListener interface:
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }
} // end class SimpleStamper
MouseMotionListeners and Dragging
Whenever the mouse is moved, it generates events. The operating system of
the computer detects these events and uses them to move the mouse cursor on
the screen. It is also possible for a program to listen for
these "mouse motion" events and respond to them. The most common
reason to do so is to implement dragging.
Dragging occurs when the user moves the mouse while holding
down a mouse button.
The methods for responding to mouse motion events are defined in
an interface named MouseMotionListener. This interface specifies
two event-handling methods:
public void mouseDragged(MouseEvent evt);
public void mouseMoved(MouseEvent evt);
The mouseDragged method is called if the mouse is moved while
a button on the mouse is pressed. If the mouse is moved while no mouse button
is down, then mouseMoved is called instead. The parameter, evt,
is an object of type MouseEvent.
It contains the x and y coordinates of the mouse's location.
As long as the user continues to move the mouse, one of these methods will be
called over and over. (So many events are generated that it would be
inefficient for a program to hear them all, if it doesn't want to do
anything in response. This is why the mouse motion event-handlers are
defined in a separate interface from the other mouse events. You can
listen for the mouse events defined in MouseListener without automatically hearing
all mouse motion events as well.)
If you want your program to respond to mouse motion events, you must
create an object that implements the MouseMotionListener interface,
and you must register that object to listen for events. The registration
is done by calling a component's addMouseMotionListener method.
The object will then listen for mouseDragged and mouseMoved
events associated with that component. In most cases, the listener object
will also implement the MouseListener interface so that it can respond
to the other mouse events as well. For example, if we want an applet to
listen for all mouse events associated with the applet, then the definition
of the applet class has the form:
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class Mouser extends Applet
implements MouseListener, MouseMotionListener {
public void init() { // set up the applet
addMouseListener(this);
addMouseMotionListener(this);
. . . // other initializations
}
.
. // Define the seven MouseListener and
. // MouseMotionListener methods. Also, there
. // can be other variables and methods.
Here is a small sample applet that displays information about mouse
events. It is programed to respond to any of the seven different kinds of
mouse events by displaying the coordinates of the mouse, the type of event,
and a list of the modifier keys that are down (Shift, Control, Meta, and Alt).
Experiment to see what happens when you use the mouse on the applet. The source
code for this applet can be found in SimpleTrackMouse.java.
I encourage you to read the source code. You should now be familiar with
all the techniques that it uses. (The applet flickers annoyingly as the
mouse is moved. This is something that can be fixed with a technique called
"double buffering" that will be covered in Section 7.1.)
It is interesting to look at what a program needs to do in order to respond to dragging
operations. In general, the response involves three methods: mousePressed(),
mouseDragged(), and mouseReleased(). The dragging gesture starts when
the user presses a mouse button, it continues while the mouse is dragged,
and it ends when the user releases the button. This means that the programming for the response
to one dragging gesture must be spread out over the three methods! Furthermore, the
mouseDragged() method can be called many times as the mouse moves.
To keep track of what is going on between one method call and the next,
you need to set up some instance variables.
In many applications, for example, in order to process a mouseDragged
event, you need to remember the previous coordinates of the mouse. You can store
this information in two instance variables prevX and prevY of
type int. I also suggest having a boolean variable, dragging,
which is set to true while a dragging gesture is being processed. This
is necessary because not every mousePressed event is the beginning of a
dragging gesture. The mouseDragged and mouseReleased methods can use
the value of dragging to check whether a drag operation
is actually in progress. You might need other instance variables
as well, but in general outline, the code for handling dragging looks like this:
private int prevX, prevY; // Most recently processed mouse coords.
private boolean dragging; // Set to true when dragging is in process.
. . . // other instance variables for use in dragging
public void mousePressed(MouseEvent evt) {
if ( we-want-to-start-dragging ) {
dragging = true;
prevX = evt.getX(); // Remember starting position.
prevY = evt.getY();
}
.
. // Other processing.
.
}
public void mouseDragged(MouseEvent evt) {
if ( dragging == false ) // First, check if we are
return; // processing a dragging gesture.
int x = evt.getX();
int y = evt.getY();
.
. // Process a mouse movement from (prevX, prevY) to (x,y).
.
prevX = x; // Remember the current position for the next call.
prevY = y;
}
public void mouseReleased(MouseEvent evt) {
if ( dragging == false ) // First, check if we are
return; // processing a dragging gesture.
dragging = false; // We are done dragging.
.
. // Other processing and clean-up.
.
}
As an example, let's look at a typical use of dragging: allowing the user
to sketch a curve by dragging the mouse. This example also shows many other
features of graphics and mouse processing. In the following applet, you
can draw a curve by dragging the mouse on the large white area. Select
a color for drawing by clicking on one of the colored rectangles on the
right. Note that the selected color is framed with a white border. Clear
your drawing by clicking in the square labeled "CLEAR".
(This applet still has the old problem that the drawing will disappear if
you cover the applet and uncover it.)
You'll find the complete source code for this applet in the file
SimplePaint.java. I will discuss a few
aspects of it here, but I encourage you to read it carefully in its entirety.
There are lots of informative comments in the source code.
The applet class for this example is designed to work for any reasonable
applet size, that is, unless the applet is too small. This means that
coordinates are computed in terms of the actual width and height of the
applet. (The width and height are obtained by calling getSize().width
and getSize().height.) This makes things quite a bit harder than
they would be if we assumed some particular fixed size for the applet.
Let's look at some of these computations
in detail. For example, the command used to fill in the
large white drawing area is
g.fillRect(3, 3, width - 59, height - 6);
There is a 3-pixel border along each edge, so the height of the drawing area is 6 less than
the height of the applet. As for the width: The colored rectangles are
50 pixels wide. There is a 3-pixel border on each edge of the applet.
And there is a 3-pixel divider between the drawing area and the colored rectangles.
All that adds up to make 59 pixels that are not included in the width of the
drawing area, so the width of the drawing area is 59 less than the width of the applet.
The white square labeled "CLEAR" occupies a 50-by-50 pixel
region beneath the colored rectangles. Allowing for this square,
we can figure out how much vertical space is available for the seven colored
rectangles, and then divide that space by 7 to get the vertical space available
for each rectangle. This quantity is represented by a variable,
colorSpace. Out of this space, 3 pixels are used as spacing
between the rectangles, so the height of each rectangle is colorSpace - 3.
The top of the N-th rectangle is located (N*colorSpace + 3) pixels
down from the top of the applet, assuming that we start counting at zero. This is because
there are N rectangles above the N-th rectangle, each of
which uses colorSpace pixels. The extra 3 is for the border at the
top of the applet. After all that, we can write down the
the command for drawing the N-th rectangle:
g.fillRect(width - 53, N*colorSpace + 3, 50, colorSpace - 3);
That was not easy! But it shows the kind of careful thinking and precision
graphics that is sometimes necessary to get good results.
The mouse in this applet is used to do three different things: Select a
color, clear the drawing, and draw a curve. Only the third of these involves
dragging, so not every mouse click will start a dragging operation. The
mousePressed routine has to look at the (x,y) coordinates
where the mouse was clicked and decide how to respond. If the user
clicked on the CLEAR rectangle, the drawing area is cleared by
calling repaint(). If the user clicked somewhere in the strip of
colored rectangles, the selected color is changed. This involves computing
which color the user clicked on, which is done by dividing the y
coordinate by colorSpace. Finally, if the user clicked on the
drawing area, a drag operation is initiated. A boolean variable, dragging,
is set to true so that the mouseDragged and mouseReleased
methods will know that a curve is being drawn. The code for this follows the
general form given above. The actual drawing of the curve is done in the mouseDragged
method, which just draws a line from the previous location of the mouse to
its current location.