New to Java? We'll help you get started with our revised beginner's tutorial, or our free online textbook.
|
Get the latest Java books |
|
h t t p : / /w w w . j a v a c o f f e e b r e a k . c
o m /
|
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 eventThis 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 ClickableRandomStringsOften, 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 SimpleStamperMouseMotionListeners 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.
[ Next Section | Previous Section | Chapter Index | Main Index ]