Contents |
|
|
|
|
|
|
|
|
|
As we saw in the chapter on windows, managing a user interface is based on the concept of events . An event is usually a user-initiated action, such as pressing a key or clicking a mouse. However, other systems also provide different types of events, such as a timer that triggers an event at certain intervals. Another possibility would be the arrival of a data packet from the internet.
The question arises as to when events are processed. There are three possibilities.
So far, we have only encountered one type of event handling, namely the processing of the Repaint event in a graphical component. This was achieved by overriding the paint method. This type of processing by overriding is now used very restrictively in Java.
Java's event handling relies on objects that can receive events. These objects are called event listeners . They implement certain interfaces and thus certain methods that handle the events. Furthermore, the objects must register with the event creators. Multiple listeners can register with a single creator.
As a side effect of the listener model, the implementation of the user interface elements and their arrangement are separated from the program logic, which lies in the processing of events. This allows the appearance of the program and its functionality to be considered separately.
As an example, we will consider the event of a button being pressed. A JButton is inserted into a window. A method that terminates the program acts as an event listener.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
class CloseButton extends JButton implements ActionListener
{
public CloseButton ()
{
super("Close");
addActionListener(this);
}
@Override
public void actionPerformed (ActionEvent e)
{
System.exit(0);
}
}
public class ButtonTest
{
public static void main (String args[])
{
JFrame F = new JFrame();
F.setTitle("Test");
F.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Register as an Action Listener:
F.add(new CloseButton());
// Show:
F.pack();
F.setLocationRelativeTo(null);
F.setVisible(true);
}
}
As you can see, this example creates a new instance of an ActionListener to react to the button being pressed. This instance must then be registered as an ActionListener with the button. If the button is pressed, it notifies all registered ActionListeners by calling their actionPerformed() method. An instance of ActionEvent, containing further information about the event, is passed as a parameter.
One button can control multiple ActionListeners. An ActionListener can remove itself from the list of event receivers using the `removeActionListener()` function.
This example also shows how the user interface structure is separated from event handling. Ultimately, any class can act as a listener that processes the event.
The example can also be organized differently by writing a custom JFrame class. This class can then register itself as an event listener and query the event source in a routine. It looks like this.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
class TestFrame extends JFrame
implements ActionListener
{
JButton Close; // The close button
/**
* Initialize the window. It contains only a close button.
*/
public TestFrame ()
{
setTitle("Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Insert button:
Close = new JButton("Close");
Close.addActionListener(this);
add(Close);
pack();
}
/**
* The ActionListener, currently only for the button
*/
@Override
public void actionPerformed (ActionEvent e)
{
if (e.getSource() == Close)
{
dispose();
}
}
}
public class Test
{
public static void main (String args[])
{
JFrame F = new TestFrame();
F.setLocationRelativeTo(null);
F.setVisible(true);
}
}
The main program now simply creates an instance of TestFrame. This class handles everything itself, including managing the events from its buttons. Since we might want to add multiple buttons, we test where the event originates.
As an example of mouse events , we will react to a click inside a TestCanvas. The goal is simply to output the coordinates of the click.
The interface here is called MouseListener and implements five methods. We register the canvas itself as a MouseListener. The event is called MouseEvent and has methods such as getX() and getY(), which can be used to retrieve the coordinates of the click.
In this example, the component itself registers as an event listener.
import java.awt.Canvas;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
class TestCanvas extends Canvas implements MouseListener
{
public TestCanvas ()
{
addMouseListener(this);
}
// MouseListener methods
@Override
public void mousePressed (MouseEvent e)
{
System.out.println(e);
}
@Override
public void mouseReleased (MouseEvent e)
{
System.out.println(e);
}
@Override
public void mouseClicked (MouseEvent e)
{
System.out.println(e);
System.out.println(
"Mouse click at " + e.getX() + "," + e.getY());
}
@Override
public void mouseEntered (MouseEvent e)
{
System.out.println(e);
}
@Override
public void mouseExited (MouseEvent e)
{
System.out.println(e);
}
}
public class Test
{
public static void main (String args[])
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TestCanvas());
f.setSize(300, 300);
f.setVisible(true);
}
}
Running this example shows that `mouseClicked()` is only called when the mouse is pressed and released at the same point. It also shows that double-clicks are difficult to detect. We'll have to postpone the necessary techniques for that.
To track mouse movement, you need a MouseMotionListener . The technique is quite similar. Refer to the Java documentation for the corresponding classes. To connect drawing routines to these events, you can use repaint() . However, it may be necessary to set up a graphics buffer and transfer the drawn graphic as a whole. We will cover these techniques later.
Keyboard events are handled by the KeyListener interface. You need to implement three methods. Here's some example code.
import java.awt.Canvas;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
class TestCanvas extends Canvas implements KeyListener
{
public TestCanvas ()
{
addKeyListener(this);
}
// KeyListener methods:
public void keyPressed (KeyEvent e)
{
if (e.isActionKey())
System.out.println("Action-Key : " + e.getKeyCode());
else if (e.getKeyCode() == KeyEvent.VK_DELETE) // Bug?
System.out.println("Action-Key : " + e.getKeyCode());
}
public void keyReleased (KeyEvent e)
{
}
public void keyTyped (KeyEvent e)
{
System.out.println("Character : " + e.getKeyChar());
}
}
As you can see, regular letter keys do not generate keyTyped() calls, and special keys do not. These are handled using keyPressed(). Due to a bug, the Delete key is not recognized as an action key.
The action key codes are all documented in KeyEvent. Examples are VK_F1, etc.
As another example of an event listener, we will look at WindowListener. This interface is used to react to events that affect the window's state. It implements a whole set of methods. The TestFrame class must implement all of these methods, even though only one is needed. This single method is windowClosing() and is called when the user clicks the close button in the window frame, or when the operating system wants to close the window.
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.JFrame;
class Test extends JFrame
implements WindowListener
{
public Test ()
{
super("Test Frame");
setSize(300, 300);
addWindowListener(this);
}
// Methods of WindowListener:
@Override
public void windowActivated (WindowEvent e)
{
}
@Override
public void windowClosed (WindowEvent e)
{
}
@Override
public void windowClosing (WindowEvent e)
{
// Close the window and exit the program:
dispose();
System.exit(0);
}
@Override
public void windowDeactivated (WindowEvent e)
{
}
@Override
public void windowDeiconified (WindowEvent e)
{
}
@Override
public void windowIconified (WindowEvent e)
{
}
@Override
public void windowOpened (WindowEvent e)
{
}
public static void main (String args[])
{
JFrame F = new Test();
F.setVisible(true);
}
}
We've chosen a different method here to test our program. The window class is the test itself, and it also contains the main program, `main`. This is a good way to incorporate test routines into classes.
There are a number of other event listener interfaces, all of which listen for specific types of events (mouse events, keyboard input, etc.). Here is one.List.
|
Event Class |
Interface |
Methods |
Sources |
|---|---|---|---|
|
ActionEvent |
ActionListener |
actionPerformed() |
Button |
|
AdjustmentEvent |
AdjustementListener |
adjustmentValueChanged() |
Scrollbar |
|
ComponentEvent |
ComponentListener |
componentHidden() |
Component |
|
ContainerEvent |
ContainerListener |
componentAdded() |
Container |
|
FocusEvent |
FocusListener |
focusGained() |
Component |
|
ItemEvent |
ItemListener |
itemStateChanged() |
Checkbox |
|
KeyEvent |
KeyListener |
keyPressed() |
Component |
|
MouseEvent |
MouseListener |
mouseClicked() |
Component |
|
|
MouseMotionListener |
mouseDragged() |
Component |
|
TextEvent |
TextListener |
textValueChanged() |
TextComponent |
|
WindowEvent |
WindowListener |
windowActivated() |
Window |
Many of the event sources listed here are unfamiliar to us. However, note that Frame is derived from Window and can therefore also be a source of a WindowEvent . Similarly, all the components we've discussed so far (Canvas, Panel, etc.) are derived from Component. The same applies to the Swing classes prefixed with J, which can usually generate the same events.
To prevent a class from becoming bloated with too many methods of an interface, there are adapter classes . These classes implement the interface with empty methods. You only need to override the corresponding method.
In the following example, we use the WindowAdapter, which implements the WindowListener interface. We simply override the windowClosing() method.
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
class Closer extends WindowAdapter
{
@Override
public void windowClosing (WindowEvent e)
{
System.exit(0);
}
}
class Test extends JFrame
{
String Close = "Close";
public Test ()
{
super("Test Frame");
setSize(300, 300);
addWindowListener(new Closer());
}
public static void main (String args[])
{
JFrame F = new Test();
F.setVisible(true);
}
}
This becomes even simpler if you declare Closer as an inner class . To do this, simply copy the declaration of Closer into the TestFrame class. The internal relationship will then become clear. Furthermore, you can use the variables of TestFrame.
class TestFrame extends Frame implements ActionListener
{
...
class Closer extends WindowAdapter
{
public void windowClosing (WindowEvent e)
{
dispose(); System.exit(0);
}
}
...
}
Everything else remains the same. Note that the inner class can access the variables of the enclosing class (in this case, Close).
The adapters for the other events are called MouseAdapter , KeyAdapter , etc.
But it can be even more concise. Instead of declaring a class at all, you use anonymous classes . The declaration is done directly within the `new` statement. This is a very compact way to declare child classes and create an instance immediately. It's clear that this class definition cannot be reused.
The instance of the anonymous class can access the variables of the class in which it was declared, just like a locally defined inner class.
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
class Test extends JFrame
{
String Close = "Close";
public Test ()
{
super("Test Frame");
setSize(300, 300);
addWindowListener(
new WindowAdapter()
{
@Override
public void windowClosing (WindowEvent e)
{
dispose();
System.exit(0);
}
});
}
public static void main (String args[])
{
JFrame F = new Test();
F.setVisible(true);
}
}
This example shows an anonymous inner class, namely a child of WindowAdapter. The syntax is
new VaterKlasse ( ConstructorParameter ) Class definition