![]() | Monkeys at Keyboards: The Javanomicon © Michael James Heron | ||||
| Topic: Java Programming Level: 2 Version: delta | |||||
26 - More Listener Objects | |||||
| Previous | Table of Contents | Next |
| Forum |
| Chapter Objectives |
By the end of this chapter, the reader will be able to:
|
And that's only a small taste of what we've accomplished. However, in the process we've neglected a number of the peripheral areas of Java in order to focus on those parts of the language that are directly related to the subjects we were investigating. For the programs we have written, this hasn't posed a serious problem... or even a problem at all. It becomes a problem later, when we realise there are some programs that we simply cannot write because we have never covered the concepts. In this chapter, we're going to look at some of the other Listener objects provided by Java. We can use these to catch some interesting behaviour, such as that triggered by the mouse and by the keyboard.
There are an enormous number of listener objects available as part of the Java framework. We've seen a few of these in the form of ActionListener, AdjustmentListener and ItemListener. But there are more, so many more... and these range from the extremely useful (such as the ones we'll discuss in this chapter) to the exceptionally specialised (such as TreeNodeListener). The number of events triggered by a standard Java program are staggering... we make use of listener objects to restrict and manage this complexity. We have to specifically indicate our interest in the occurrence of a particular kind of event associated with a particular kind of component - Java does the rest, but it needs us to tell it in what we're interested. The benefit of this is that we can deal only with those events that have a direct influence on the way our programs work. The downside is that unless we know what we're looking for, we may miss a lot of neat events that we could use to make our programs more effective or simply more interesting.
Your mouse is a constant source of events in a Java program. It is spewing them out, dozens every minute... but since we've never been interested in them before, we've never done anything about them. Your mouse triggers events when:
There are other situations, and we'll look at those in the course of this chapter. We can create adapters to deal with each of these events in the way we discussed in the last chapter - we're not going to do that here. We're going to implement the functionality within our application/applet, just like in the Good Old Days so as not to confuse form with function. We need to implement an interface to make use of mouse events - this interface is called MouseListener. Individual components must register their interest in mouse events by using the addMouseListener method. Whichever object implements the listener interface must provide code for the following methods:
Each of these methods takes a parameter of type MouseEvent.
MouseEvents are slightly more complex than the other event objects we've looked at because they carry some more information with them. This information relates to the state of the mouse at the point the event was triggered. We make use of the MouseEvent object that gets passed to each handler method to query this information to ensure that our code behaves properly. The MouseEvent object contains two methods of particular note: getX, and getY. These methods return the relative X and Y co-ordinates of the mouse when the event was triggered. For example, we can modify the code of mouseEntered to set the text of the label to the entry point for the pointer:
MouseEvent also has a method called getButton that returns which button was pressed... it returns a different value for the left, middle and right buttons, so you can ensure that the correct functionality is triggered when one is pressed. We check the return value of getButton against one of the predefined constants in the MouseEvent class:
Sometimes we don't want to find out about a single button click - we only want double clicks. There's a method in the MouseEvent object called getClickCount that returns how many times the button was clicked in a set interval. If the integer returned from this is two, it's a double-click:
The MouseListener is just one of a range of listener objects that relate to mouse movements. They all work in the same way, which indeed is the same way as all our other listener objects that we've used up until now.
The MouseListener interface allows us to catch one subset of the events that go with a mouse, and these are the events that are related to a particular component. Sometimes we just want to catch the events that go with mouse movements in general, without them being associated with any given component. We use a MouseMotionListener for this. The interface for MouseMotionListener requires two methods to be implemented:
As with a MouseListener, these methods take a parameter of type MouseEvent. We use addMouseMotionListener to register our interest in these events, but unlike MouseListener we register our interest on an applet/application as a whole in the same way we did with addWindowListener way back in the chapter on free standing applications:
We're going to look at a slightly longer example of this by writing an application that tracks the motion of our mouse with a pair of eyes:
This is a good way to get to grips with our mouse events, because it involves writing the code that ensures the eyes react to the pointer when it is in certain locations. First, we should discuss how we can get the eyes to follow the mouse. This is easier than it sounds - we simply draw the pupils as close to the mouse pointer as we can. Obviously this won't work without some restrictions, or the eyes would be glued to the mouse pointer in a most unappealing way. We have to provide some restrictive bounds within which the eyes can move freely. The greater white oval of the eye is fixed. It's only the black pupil that is going to move. We've already seen how we can draw circles and ovals on the screen - in our first applet chapters and in a more recent chapter where we did some basic animation. We simply use the fillOval method of the Graphics class, which takes four parameters representing the bounds within which the oval will be drawn. It's just a case of deciding where we want them to be drawn, and how big they're going to be:
Once we've drawn these, we need to decide on some bounding rectangle within these larger ovals of the eyes that restrict the movement of the pupil. We can't simply use the same bounds because then the pupils will move outside the oval of the eyes, which really isn't what we want. Instead we must provide some values that define a bounding rectangle within each of the ovals. The values for this must be chosen in such a way that we ensure that the black eyeball never leaves the white oval, even at the extreme edges.
The dimensions of this bounding rectangle are going to be dependant on how much movement we are willing to allow. In this case, we will set them as 10 length ways and 30 height ways. We also need to choose where they are going to be drawn, and planning will give us the following dimensions for the bounding rectangles for movement:
Now, here's the slightly tricky part - there's no point in actually drawing these rectangles because they won't stop the movement of the pupils. The best we can do is simply hard code the restrictions. Drawing it out simply makes it clearer to ourselves where they lie. Okay, so let's start writing the code. We're going to do this as a JPanel within an application, so we need to write the framework for this and implement the MouseMotionListener interface:
The code for dealing with the mouse events is going to be placed within mouseMoved, and the paintComponent method will do the actual drawing. We need some way of communicating between the two, so we'll provide two class wide variables of type integer, which we'll call x and y... and we'll set them to the appropriate value in the mouseMoved event before calling the repaint method:
Okay... now we need to handle the drawing part. First of all, let's draw the larger white ovals of the eyes - that's a straightforward procedure that we already know all the values for:
Compile and run, and we get two spooky white ovals on our applet. We really need to provide some definition around them, so we'll also draw an empty black oval around each:
Okay, the next step is to add in our boundary values. We can't do this by drawing rectangles, we just need to store each of the values in an appropriate integer variable:
Both pupils will have the same rules regarding their motion height-ways, so we use maxht and minht for both of them. Minht is the lowest that the X value can legitimately go, and maxht is likewise the highest. We then use a separate minlen and maxlen for each, since their motion in the Y axis will be dependant on which eyeball they are in. All these variables go into the paint method. We then need to do the boundary checking for each eyeball - this works exactly the same way as the boundary checking for the bouncing ball that we wrote in chapter (sound and vision). We need to know where the top left hand corner of each pupil is - we'll need four class wide variables for this:
These will change as the motion of the eyeball changes, in the same way as our animated bouncing ball. Once we have these, we can implement the boundary checking. First in the X axis, because we need to check two separate values here:
And then in the y axis, because both eyes will move the same way:
Finally, we just need to draw the eyeballs:
So in the end, our finished paintComponent method looks like this:
We can then apply our new moving eyes JPanel to a proper application to give us the potential for Great Creepiness in our programs:
How neat is that? I'll give you a clue - it's very neat!
Finally on the subject of mouse listeners, we are going to look at the MouseWheelListener, which listens for mouse wheel events (no, seriously). We're going to alter our moving eyes a little so that we can change the size of the pupils by scrolling the mouse wheel. Alas, MouseWheelListener is a very new addition to Java - it appears only in the 1.4 version of the development kit. If you are using an earlier version of Java, you will not be able to make use of it. MouseWheelListener has only one method that it needs implemented, and that is the mouseWheelMoved method, which takes a parameter of MouseWheelEvent. The MouseWheelEvent object has a useful method called getWheelRotation that returns how much the wheel was turned, and in what direction. If it returns a positive number, the wheel has been scrolled upwards. If it returns a negative number, the wheel has been scrolled downwards. With this in mind, we're going to make a small change to our paintComponent method, in that we will no longer be drawing our pupils at size 10... instead we'll be drawing them according to the value of a class wide integer variable called size. After implementing MouseWheelListener, we have only to register our interest in mouse wheel events, and add our required method to provide the required functionality. We register our interest on a JPanel as a whole.
And then implement the functionality in the mouseWheelMoved method. Our modified MovingEyes class now looks like this:
And there we go - in addition to having eyes that follow our pointer around the screen, we have eyes that grow and shrink as we scroll the wheel. Neato!
Now that we've discussed how to make use of mouse events, we should look at the other main input device we have available - the keyboard. Many Swing components are already keyboard friendly to a degree - for example, they allow for shortcut keys to be set via the setMnemonic method we discussed in chapter five. In most cases, there is little need to implement keyboard event handling since it is done automatically. We don't need to write handling code to allow us to type into a JTextField, for example. In most cases, we only need to implement a key listener of some sort if we are writing an application that involves key action without it going through a component (like a game), or when we want an application to respond to non-alphanumeric keys, like the cursors or the function keys. The interface we must implement to make use of keyboard events is called KeyboardListener. It requires the following methods to be implemented:
Each of these methods takes a KeyEvent parameter, which comes with a very useful method called getKeyCode. This returns an integer value that represents the key that has been pressed. For the alpha-numeric keys, this corresponds to their Unicode value, so it is possible to place it directly into a char variable. The KeyEvent class also contains a number of very useful constants that we can compare against the returned value from getKeyCode:
And so on... The Java documentation contains a fuller description for the key codes attached to the KeyEvent class. We can use these key codes to implement any key functionality we want attached to our application. For example, for a simple typewriter application:
Sometimes we want to be able to have our applications respond differently depending on how long between keyboard presses. There's a method in KeyEvent called getWhen, and it returns the number of milliseconds since the start of the standard Unix epoch. The getWhen method is timestamped with the instant the KeyEvent was created. Alas, there is a problem in the way key events are generated. Consider a simple example of a system that lets us setup a morse code message by tapping the space-bar. If we give it a short tap we should generate a dot. If we give it a long tap, we should generate a dash. A short tap is defined as a key press that lasts less than .2 seconds, and a long tap is anything else. Our getWhen method should handle this for us, but alas, we get some strange behaviour. We get the time that a key was pressed by calling getWhen in the keyPressed method and storing it in a class-wide variable. When the key is released (which calls keyReleased) we then compare the time the key was pressed against the time it was released. Theoretically, that should give us the delay:
Alas, when we run this application, all we ever get are dots! Even when we hold the key for many seconds, it still generates a dot. How peculiar! The reason for this may be found in the autorepeat mechanism provided by many operating systems. Hold down a key and it will cause the character to repeat until you release it. Alas, Java simulates this mechanism by calling keyPressed for every repeat character... which means the then timestamp gets set to when the last character was repeated. The delay between repeats is about fifty milliseconds, so it never draws a dash. We deal with this by providing a simple mechanism... when we press a key for the first time, we set the time-stamp and set a boolean variable to true. We check when we enter the keyPressed method to see if this boolean variable is true. If it is, we simply return from the method. In keyReleased, we reset this boolean variable back to false and then calculate the delay:
Now we've managed to work our way past the autorepeat mechanism and implement a simple morse code system.
When a key has been pressed and the appropriate listener object catches the event, the getSource method of the KeyEvent parameter will return the object that currently has focus. This is the case even if the current source object is not one we would normally associate with a key event. This means that if you are basing your action on the source of the event it can be quite tricky to ensure a proper course of execution based on what you get out of getSource. For example, consider a simple game where we have a selection of options and a game window:
If the user presses an arrow key when the game is over, we'd like it to move the focus between buttons. Otherwise we'd like it to control the action in the game window. However, after having pressed the New button, it is that button that has focus... and so getSource of they key events will return that button as their source until the game window gains focus. In most programs, this probably won't be a problem. However, in those that it will be, it is necessary for the application to ensure focus is reset properly. This is also a very useful technique for general user interface design. For example, when filling out an online questionnaire and finding that one value doesn't meet the required format, it is user friendly to ensure the offending JTextField has the focus so the user can type directly into it rather than have to find it and click on it themselves. We can use the requestFocus method of a component to ensure it gains the focus in an application. We can register interest in focus events by implementing FocusListener in our application. We register individual components as the source of our interest in the events. The FocusListener interface demands two methods be implemented
Both of these methods take a FocusEvent as their parameter. Some objects do not gain the focus in an application/applet by default. For example, labels will never gain a focus by being clicked on unless you explicitly tell Java they should be a valid focus component. This can be done by calling setFocusable on them:
We only need register a FocusListener when we have some focus handling functionality that is out of the ordinary. For example, if we are displaying an animation we may want to halt the animation loop if the display window loses focus. We can do this by implementing the required code in the focusLost method, and then restarting the animation loop can be handled in the focusGained method.
Java is full of events. It triggers many more events than we can possibly deal with in the course of an application's lifetime. It provides us with a way of managing this information overload through the use of listener objects. We specifically register an interest in a particular kind of event through using the appropriate addXXXlistener method on the appropriate component. We then must provide the method code for dealing with these events. Some of these events are tremendously complicated and require lots of understanding of the underlying structure of Java before they can be used properly. Most are sufficiently specialised that they are useful only for very specific situations and cannot be expanded into generally useful tools. Mouse events and keyboard events are some of the more useful events that Java throws our way in the course of normal execution of a program. Mouse events in particular are triggered all the time - the sheer volume of events called means that dealing with all possible mouse events requires the implementation of no fewer than three interfaces. Largely all event handling follows the same structure. We register an interest in a kind of event, we implement the interface, we provide the method handling bodies, and we're done. The interested reader is directed towards the Java document which is overflowing with examples of other listener objects to suit every occasion. Further ReadingThe following table details further reading on the topic in this chapter, and also any external resources that you may find useful.
|
| Previous | Table of Contents | Next |
© 2004-2006 Michael James Heron