The way in which application code is called varies between the different windowing systems. In some, the application programmer has to write an event loop handler themselves, and when an event occurs the application has to figure out what to do with it. In other systems some assistance may be given with this, where events are dispatched to graphical objects automatically. When they arrive, application code still has to work out what type of event has occurred. In yet other systems, the application merely needs to register an event handler with an object, and the handler will be called whenever the right event occurs in the right object, without any further need for intervention by the application programmer.
Java in release 1.1 is now up to its third event processing model! The first one - called the ``old'' event model was abandoned before Java left beta stage. It was replaced by the ``new'' event model for Java 1.0. Unfortunately, two things marred this transition to the new model:
Both the old and new models had serious Software Engineering deficiences that led to poor quality code as soon as applications started to grow in scale. A solution to this was already known as the Command Pattern, and this was adopted for the third event model for the Java AWT. The Command Pattern was renamed ``delegation'' by Sun engineers, and forms a much cleaner way of handling events. Basically, it allows the application to register handlers (called listeners) with graphical objects, which are called when suitable events arrive.
There are complex implementation layers and issues arising from the third event model. When the user performs an action such as a keypress, a native code event is generated. This is firstly handled by native code at, say, the Windows or X level. The event may be caught by the native toolkit or ignored. If caught, two things may happen:
When an event is handed to the Java layer, it is changed into a Java event. Java code looks to see if there is an event listener using the third event model. If there is, this is used. If there is not, then the second ``new'' event model is used. Sometimes there is no listener but the third model should be used anyway. This can be forced.
If all this sounds grim, then be reassured: if you only want to do simple things, the delegation event model lets you do them simply. Most of the time you only want to simple things.
For example, suppose we are building a Web browser. We may have a Back button to return to the previously displayed page. When we click on this Back button, the application will need to reload the last page into the browser window and reset history lists, current URL pointers, etc. This is not GUI code, though it is clearly application code that affects GUI objects as well as other application objects of the browser. The Back function can also be invoked in other ways, such as by a hot-key or by menu selection. Not only is the behavior application specific, but there is more than one way way of invoking this behavior. A listener object encapsulates this behavior, and should be registered with the Back button, the hot-key combination and the menu selection.
When a listener is invoked, it has to be by one of its methods.
There could be a standard method such as execute()
or by one customised to the event that caused it. The JDK 1.1 has
chosen the second method.
So if the mouse is clicked in a Button, then an action event is generated
(invisibly to most applications), and the actionPerformed()
method is called on any action listener. On the other hand, whenever
a key is pressed in a Component, then the method
keyPressed()
is called in any key event listener.
This could have been more simply if simplicity was the only driving force behind this event model: however, Java Beans imposes extra requirements which lead to these multiple event types.
The methods defined for listeners are done using interfaces. This is an absolutely appropriate use of the interface mechanism. Since listeners contain application code, they will probably have an inheritance based on the application, not on some vagaries of the GUI side. All the listener needs to do is to implement certain methods, so that the internal event handling code can call the right method. So, for example, the ActionListener is defined by
public interface ActionListener extends EventListener { public abstract void actionPerformed(ActionEvent e) }whereas KeyListener is defined by
public interface KeyListener extends EventListener { public abstract void keyPressed(KeyEvent e); public abstract void keyReleased(KeyEvent e); public abstract void keyTyped(KeyEvent e); }
Listener objects - just like any object - get knowledge about other objects in three ways
Here is a trivial application just to show how listeners are created and used. The application has a Button on the left, a Button on the right and a Label in the middle. The left Button has the label ``Left'', and the right one has the label ``Right'' When either Button is pressed, the text showing in the Label is set to either ``Left'' or ``Right'' i.e. the label of the Button pressed.
import java.awt.Button; import java.awt.Label; import java.awt.event.ActionListener; /** * This application consists of two Buttons with a Label * between them. When a Button is pressed, its text is * set in the Label * * @author Jan Newmarch */ public class DelegateDemo extends Frame { public static void main(String argv[]) { new SimpleEvent().show(); } public DelegateDemo() { // create the GUI objects Button left = new Button("Left"); Button right = new Button("Right"); Label label = new Label("Center"); // set their geometry add(left, "West"); add(right, "East"); add(label, "Center"); pack(); // create a listener and add it to each Button SimpleListener simple = new SetText(label); left.addActionListener(simple); right.addActionListener(simple); } } /** * A listener object that is invokded when a Button is activated * It finds the Button's label and sets it in a Label */ class SetText implements ActionListener { private Label label; public SetText(Label l) { // the listener needs to know the Label it will act on label = l; } public void actionPerformed(java.awt.event.ActionEvent e) { // get the label showing in whichever Button was pressed String name = e.getActionCommand(); // set this in the Label object label.setText(name); } }
AWTEvent
. If you are familiar with the old or new event
models, then you will have used the Event
class in your
event handling code. Forget about that class now. It is superceded by
AWTEvent
. Forget also about all the fields and values
that are present in Event
- they are all absent from
AWTEvent
. The class AWTEvent
has only one
field, id
, and that is protected anyway. The
AWTEvent
is very simple, and you don't even need to look
at it unless you are doing arcane things (which we will do
later).
For each type of event that is present in the delegation model, there is a separate class. The classes are divided into two types: low-level input or window events that every GUI object receives, and higher-level semantic events that only have meaning for particular GUI objects. In addition there are low-level window events that are only received by Frame and Dialog. This kind of division of types is not captured anywhere in the language, rather it is a guide to the programmer.
The low-level events for any Component are
Class | id |
---|---|
ComponentEvent | COMPONENT_MOVED |
COMPONENT_RESIZED | |
COMPONENT_SHOWN | |
COMPONENT_HIDDEN | |
FocusEvent | FOCUS_GAINED |
FOCUS_LOST | |
KeyEvent | KEY_PRESSED |
KEY_RELEASED | |
KEY_TYPED | |
MouseEvent | MOUSE_CLICKED |
MOUSE_DRAGGED | |
MOUSE_ENTERED | |
MOUSE_EXITED | |
MOUSE_MOVED | |
MOUSE_PRESSED | |
MOUSE_RELEASED |
Where are PaintEvent.PAINT etc?
The low-level window events for Frame and Dialog are
Class | id |
---|---|
WindowEvent | WINDOW_CLOSED |
WINDOW_CLOSING | |
WINDOW_DEICONIFIED | |
WINDOW_ICONIFIED | |
WINDOW_OPENED |
The semantic level events are
Class | id |
---|---|
ActionEvent | ACTION_PERFORMED |
AdjustmentEvent | ADJUST_VALUE_CHANGED |
ItemEvent | ITEM_STATE_CHANGED |
For the low-level events, the protected id
takes on
different values to distinguish between the type of event. This
value can be found from the event method getId()
.
For the semantic level events, there is only one value for
id
, so that although getId()
will give
a value, it isn't really very useful (unless you have subclassed one
of these semantic events and have your own extra id
values).
These values are used by the AWT to call appropriate methods on listeners,
and you do not usually need to look at these values.
The semantic events are generated by the following
Event | Object | Action |
---|---|---|
ActionEvent | Button | Click on Button |
List | Double-click on an item | |
MenuItem | Click on MenuItem | |
TextField | Press <Enter> key | |
AdjustmentEvent | Scrollbar | Any Scrollbar action |
ItemEvent | Choice | Select an item |
List | Select or deselect an item | |
Checkbox | ??? | |
CheckboxMenuItem | ??? |
MouseEvent
's
have two different types of listener for efficiency reasons.
Event | Listener | Method |
---|---|---|
ActionEvent | ActionListener | actionPerformed() |
AdjustmentEvent | AdjustmentListener | adjustmentValueChanged() |
ComponentEvent | ComponentListener | componentResized() |
componentMoved() | ||
componentShown() | ||
componentHidden() | ||
FocusEvent | FocusListener | focusGained() |
focusLost() | ||
ItemEvent | ItemListener | itemStateChanged() |
KeyEvent | KeyListener | keyTyped() |
keyPressed() | ||
keyReleased() | ||
MouseEvent | MouseListener | mouseClicked() |
mouseEntered() | ||
mouseExited() | ||
mousePressed() | ||
mouseReleased() | ||
MouseMotionListener | mouseDragged() | |
mouseMoved() | ||
WindowEvent | WindowListener | windowClosed() |
windowClosing() | ||
windowDeiconified() | ||
windowIconified() | ||
windowOpened() |
The elements of this table should be interpreted as follows:
ActionListener
has been registered with, say,
a Button
. When the Button
is clicked,
the method actionPerformed()
of the
ActionListener
will be called with
a single parameter: the ActionEvent
that was
generated.
KeyListener
may be registered for key events
with, say, a TextField
. When a key is pressed, the method
keyPressed()
of the KeyListener
will be called with the KeyEvent
as parameter. When the key is released, the method
keyReleased()
will be called with a different
KeyEvent
as parameter, and also, because a key has been
pressed and released (i.e. it has been typed),
the method keyTyped()
is called with a
third KeyEvent
as parameter.
ActionListener
,
AdjustmentListener
, etc. Once created, listeners
are registered with GUI objects. Of course, the GUI object has to be
capable of generating the right type of event for the listener.
Listeners are added to each object by a suitable
add<event>Listener()
call.
In the simple example we had earlier, this was done for an
ActionEvent
listener on a Button
by
SimpleListener simple = new SimpleListener(label); left.addActionListener(simple);The following table lists which objects can have which listener types added:
GUI Object | Listener |
---|---|
Button | ActionListener |
Choice | ItemListener |
Checkbox | ItemListener |
Component | ComponentListener |
FocusListener | |
KeyListener | |
MouseListener | |
MouseMotionListener | |
Dialog | WindowListener |
Frame | WindowListener |
List | ActionListener |
ItemListener | |
MenuItem | ActionListener |
Scrollbar | AdjustmentListener |
TextField | ActionListener |
Component
.
So every object can have Component
, Focus
,
Key
, Mouse
and MouseMotion
listeners added.
The next sections look at each event type in turn.
GUI Object | Action |
---|---|
Button | Click on Button |
List | Double-click on an item |
MenuItem | Click on MenuItem |
TextField | Press <Enter> key |
An ActionListener
can be registered with each of these
objects, and its method actionPerformed()
is called
when the user performs the indicated activity.
An ActionEvent
has two methods that the programmer may find
to be of use:
public String getActionCommand() public int getModifiers()Each
ActionEvent
carries around an ``action command''.
This command may be set by the Button
or
MenuItem
by their
setActionCommand(String)
method. There is no such method
for List
or TextField
. The value of the action
command (unless reset) is
GUI Object | action command |
---|---|
Button | label |
List | ??? |
MenuItem | label |
TextField | ??? |
The action command gives runtime information about the context in which
the event occurred. This is semantic-level information, rather than low-level
information: the text showing in the Button
is usually
enough useful information. If low-level information is required,
the source object can be obtained from the method getSource()
(or equivalent methods).
This method is inherited by every event from the superior class
java.util.EventObject
.
We shall illustrate use of action events by four programs which do basically
the same thing, but use different GUI elements to do this. The common
part of these will be a Label
which can have its
foreground color set to different values. The color can be selected
in different ways for each program, but they will all invoke the
same ActionListener
. This ActionListener
is defined by the following:
import java.awt.Component; import java.awt.Color; import java.awt.event.ActionListener; /** * An ActionListener that changes the foreground * color of a component passed in by the constructor * * @author Jan Newmarch */ public class SetColor implements ActionListener { final Color colors[] = {Color.red, Color.blue, Color.green}; final String colorLabels[] = {"red", "blue", "green"}; private Component comp; public SetColor(Component c) { comp = c; } /** * Invoked by an action event somewhere * resets the foreground of a component passed * in to the constructor */ public void actionPerformed(ActionEvent e) { String colorName = e.getActionCommand(); // search for Color matching color name for (n = 0; n < colorLabels.length; n++) { if (colorLabels[n].equals(colorName)) { // found a match, set foreground comp.setForeground(colors[n]); return; } } System.out.println("Unknown color: " + colorName); } }
The first program to use this has a row of three buttons along the
top and a label to below them. A single
SetColor
listener will be created and registered with
each button. The application looks like
The code is
import java.awt.*; import SetColor; public class ButtonColor extends Frame { public static void main(String argv[]) { new ButtonColor().show(); } public ButtonColor() { // the label that will be colored Label label = new Label("Click on button to change color"); // a panel to hold the buttons Panel panel = new Panel(); // buttons for each color Button red = new Button("red"); Button blue = new Button("blue"); Button green = new Button("green"); // set geometry panel.setLayout(new FlowLayout()); panel.add(red); panel.add(blue); panel.add(green); add(panel, "North"); add(label, "Center"); pack(); // add the listener SetColor sc = new SetColor(label); red.addActionListener(sc); blue.addActionListener(sc); green.addActionListener(sc); } }The second program to use the
SetColor
listener will just
change elements of the user interface. That is, instead of a set of
buttons, it will use a list to the left of the label. In this version
we shall only implement the double-click action, leaving single-click
till later (that uses a different type of event). The application
looks like
import java.awt.*; import SetColor; public class ListColor extends Frame { public static void main(String argv[]) { new ListColor().show(); } public ListColor() { // the label that will be colored Label label = new Label("Double-click to change color"); // the list of colors List colors = new List(3); colors.add("red"); colors.add("blue"); colors.add("green"); // set geometry add(colors, "West"); add(label, "Center"); pack(); // add the listener SetColor sc = new SetColor(label); colors.addActionListener(sc); } }Note that we have not changed the application code at all, only the GUI code. In this we are partly lucky since we are handling the same event type in both programs, but even handling different types does not cause many changes.
The third program will make the same kind of change. This time we
shall use a menu of colors to make the selection. Each item in the
menu will have the same listener added, just as for the buttons
in the first program.
The application looks like
The code is
import java.awt.*; import SetColor; public class MenuColor extends Frame { public static void main(String argv[]) { new MenuColor().show(); } public MenuColor() { // the label that will be colored Label label = new Label("Select color from menu"); // the menu bar MenuBar mb = new MenuBar(); Menu color = new Menu("Color"); mb.add(color); // menu color items MenuItem red = new MenuItem("red"); MenuItem blue = new MenuItem("blue"); MenuItem green = new MenuItem("green"); color.add(red); color.add(blue); color.add(green); // set the geometry setMenuBar(mb); add(label, "Center"); pack(); // add the listener SetColor sc = new SetColor(label); red.addActionListener(sc); blue.addActionListener(sc); green.addActionListener(sc); } }Again there is no change to the application code.
The last example gives the last variation on this theme, using a
TextField
for color selection. The application looks
like
The code is
import java.awt.*; import SetColor; public class TextFieldColor extends Frame { public static void main(String argv[]) { new TextFieldColor().show(); } public TextFieldColor() { // the label that will be colored Label label = new Label("Enter color in the TextField"); // the text field to enter the color TextField text = new TextField(20); // set geometry add(text, "North"); add(label, "Center"); pack(); // add the listener SetColor sc = new SetColor(label); text.addActionListener(sc); } }
This last piece of code is rather lacking in ease of use, as it gives no clues as to what are allowable text values!
Scrollbar
in Java 1.1.
There is not so much need to use this class
in Java 1.1, as there is also a ScrollPane
class which
looks after most details of handling a viewport onto a component.
A listener of class AdjustmentListener
is added to a
Scrollbar
by the method
public void addAdjustmentListener(AdjustmentListener)The user can interact with the scrollbar by clicking the mouse within it, by dragging the slider or by keyboard interaction such as the arrow keys or PageUp/PageDown keys. These all cause the method
public void adjustmentValueChanged(AdjustmentEvent)to be called in the listener.
There is only the one method called
despite the variety of ways that the user can interact with the
scrollbar. If the application needs to distinguish between these,
it can call the method getAdjustmentType()
on the
AdjustmentEvent
. This will return one of the four values
UNIT_INCREMENT
- down one ``line''
UNIT_DECREMENT
- up one ``line''
BLOCK_INCREMENT
- down one ``page''
BLOCK_DECREMENT
- up one ``page''
The actual value of the scrollbar can be found from the method
getValue()
of the AdjustmentEvent
.
To use this value, you need to know where on the scale from minimum
to maximum it occurs. These - and much other information - are
available from the event of class Adjustable
in which
the event occurred (right now we know this is a Scrollbar
but it could be other things in the future - for example, a
Scale
object).
ComponentEvent
. This is used for tracking movement,
resizing and visibility. An application will generally function
perfectly well by ignoring these events: the AWT toolkit will look
after things such as calling layout managers on resize.
There are occasions when an application may want to track such events. For example, a visually intensive piece of graphics such as animation should cease when the display object becomes invisible, so it will want to track visibility changes. A ``smart'' text display may want to change the size of the font used according to the amount of space it has, so it will want to track size changes.
An application wanting to track such changes registers a listener
that implements ComponentListener
. This interface
defines four methods that the listener must implement:
public void componentHidden(ComponentEvent); public void componentMoved(ComponentEvent); public void componentResized(ComponentEvent); public void componentShown(ComponentEvent);In objects of class
ComponentEvent
the id
is used
field to distinguish between four types: COMPONENT_HIDDEN
,
COMPONENT_MOVED
, COMPONENT_RESIZED
and
COMPONENT_SHOWN
. The AWT uses this value to select which
of the four listener methods to call, so you are unlikely to need
to look at this field.
The ComponentEvent
class supplies a method
public Component getComponent()if you need to determine which component has called the listener.
Here is a program to track resize and movement events on the
toplevel Frame
:
import java.awt.*; import java.awt.event.ComponentEvent; public class TrackResize extends Frame { public static void main(String argv[]) { new TrackResize().show(); } public TrackResize() { Label label = new Label(); add(label); pack(); addComponentListener(new Tracker(label)); } } class Tracker implements ComponentListener { private Label label; Tracker(Label l) { label = l; } public void componentHidden(ComponentEvent e) { // empty } public void componentMoved(ComponentEvent e) { showGeometry(e); } public void componentResized(ComponentEvent e) { showGeometry(e); } public void componentShown(ComponentEvent e) { // empty } private void showGeometry(ComponentEvent e) { Component c = e.getComponent(); Dimension d = c.getSize(); Point p = c.getLocation(); label.setText("Position: (" + p.x + "," + p.y + ") Size: (" + d.width + "," + d.height + ")"); } }
The Tracker
class inherits only from Object
.
Because it has to implement all of the ComponentListener
interface it ends up with empty implementations of the
componentHidden()
and componentShown()
methods. This may become mildly annoying if it had to be done
frequently, so there is a ``convenience'' class
ComponentAdaptor
which defines all these methods as
empty ones. Using this, you can inherit from ComponentAdaptor
and then just override the methods you need. The single inheritance
model of Java does of course then require that the listener cannot also
inherit from other classes, so this may be of limited value.
AddFocusListener()
. The listener must then implement
the methods
public void focusGained(FocusEvent); public void focusLost(FocusEvent);There are no extra methods for handling these events.
GUI Object | Action |
---|---|
Choice | Click on item |
Checkbox | Select an item |
Deselect an item | |
List | Select an item |
Deselect an item | |
Extend a selection | |
Reduce a selection |
An application wanting to follow selection changes registers an
object that implements ItemListener
. When such a change
occurs, it will have the method itemStateChanged()
called
with an ItemEvent
as parameter.
Item event handling appears to be incomplete in Java 1.1 beta.
The ItemEvent
declares methods
ItemSelectable getItemSelectable(); Object getItem();The interface
ItemSelectable
declares methods
interface ItemSelectable { int[] getSelectedIndexes(); String[] getSelectitems();Presently, the classes that implement the
ItemSelectable
interface are List
, Choice
and
Checkbox
.
Now consider the information we would be after when an item in a list
is selected: the index and the string showing. The index is given
by the getItem()
of the ItemEvent
, but it has
to be coerced to class Integer
first. The actual string is
not obtainable from the information given. The method
getItemSelectable()
returns the item that the selection
was performed on - the list. From there you can find the set of
all selected items (which may be more than one), but not
the single list item that changed state. To get at that, you have to
check and then coerce the ItemSelectable
to class
List
and then use the List
method
getItem()
to find the string selected.
// import java.awt.Label; import java.awt.Color; import java.awt.Component; // import java.awt.Frame; import java.awt.event.ItemListener; import java.awt.event.ItemEvent; /** * An ItemListener that changes the foreground * color of a component passed in by the constructor * * @author Jan Newmarch */ public class SetColor2 implements ItemListener { final Color colors[] = {Color.red, Color.blue, Color.green}; final String colorLabels[] = {"red", "blue", "green"}; private Component comp; public SetColor2(Component c) { comp = c; } /** * Invoked by an item event somewhere * resets the foreground of a component passed * in to the constructor */ public void itemStateChanged(ItemEvent e) { if (e.getStateChange == ItemEvent.SELECTED) { System.out.println(e.getItem()); //String colorName = e.getActionCommand(); // search for Color matching color name //for (n = 0; n < colorLabels.length; n++) { //if (colorLabels[n].equals(colorName)) { // comp.setForeground(colors[n]); //return; //} //} //System.out.println("Unknown color: " + colorName); } } }
import java.awt.*; public class ListColor2 extends Frame { public static void main(String argv[]) { new ListColor2().show(); } public ListColor2() { // the label that will be colored Label label = new Label("Hello World"); // the list of colors List colors = new List(3); colors.add("red"); colors.add("blue"); colors.add("green"); // set geometry add(colors, "West"); add(label, "Center"); pack(); // add the listener SetColor2 sc = new SetColor2(label); colors.addItemListener(sc); } }
TextArea
and TextField
they really have semantic content. For other
classes keyboard events are most likely to be use in implementing
keyboard traversal mechanisms, such as the use of arrow keys to move
around a grid of labels, or of the tab key to move between objects.
Key events also have an important property not shared with most other event types, and that is they can be changed by the application. For example, the application may change any text typed to lower case by mapping any upper case characters entered into lower case.
A KeyEvent
defines a large number of constants
for the special keys on the keyboard. These are known as action
keys and are given in this table:
HOME | END | PGUP | PGDN |
UP | DOWN | LEFT | RIGHT |
F1 - F12 | PRINT_SCREEN | SCROLL_LOCK | CAPS_LOCK |
NUM_LOCK | PAUSE | INSERT | ENTER |
BACK_SPACE | TAB | ESCAPE | DELETE |
KeyEvent e; ... if (e.isActionKey()) { switch (e.getKeyCode()) { case KeyEvent.HOME: ... case KeyEvent.END: ... case KeyEvent.PGUP: ... ... } }
There are related methods inherited from InputEvent
,
the super class of KeyEvent
. These allow tests for
the modifier keys:
public boolean isShiftDown(); public boolean isControlDown(); public boolean isMetaDown();The information from these can also be obtained from the method
getModifiers()
, and then comparing this to
InputEvent.CTRL_MASK
, etc.
import java.awt.*; import java.awt.event.KeyListener; public class ShowKey extends Frame { public static void main(String argv[]) { new ShowKey().show(); } public ShowKey() { Label l = new Label("Hello"); add(l); pack(); KeyInfo ki = new KeyInfo(); l.addKeyListener(ki); } } class KeyInfo implements KeyListener { public void keyPressed(KeyEvent e) { System.out.print("Down: "); printInfo(e); } public void keyReleased(KeyEvent e) { System.out.print("Up: "); printInfo(e); } public void keyTyped(KeyEvent e) { System.out.print("Typed: "); printInfo(e); } private void printInfo(KeyEvent e) { System.out.println(e.toString()); System.out.println("char: " + e.getKeyChar() + "keycode: " + e.getKeyCode()); } }
What is the difference between chars and keycodes, particularly with Unicode around?
Key events can have their value changed by the application. A simple use is to map all upper case keys down to lower case. We need to change the key value and also the modifiers value.
Discard keys for password entry.
Tab expansion replaces key.
A date entry TextField, where after each pair of numbers a '/' is added to the event queue.