Introduction
In JDK 1..6+, Events are
handled quite differently from JDK 1.0.2. This
essay will follow an single Event from creationto
cremation to give you an idea of how it all fits
together. For details, and practical coding concerns, I suggest you read some
other essays on Events such as Richard
Baldwin’s instructional essays or Jan
Newmarch’s essay on JDK 1.1+ events. I
have written a similar essay for the older JDK 1.0.2
Events.
I’m a guy who can’t use a tool until I have some model of how it
works under the hood. If you are that way, you will like this essay. If you are
the kind of programmer who simply wants to use the tool without any interest of
how it works inside, all you need pay attention to in this essay is how EventListeners
work.
Overview
Events are:
- A way one object can call a method in another, but with a delay. Instead of
doing the work right away, the work is postponed by putting a note to do it
later into a queue, so that the caller can get on with more important work.
- A flexible way of deciding at run time precisely which objects should have their
methods called when something interesting happens.
- A way for letting the callee decide when it wants to be called rather than
leaving that decision totally up to the caller.
- A way of scheduling work for other Threads to do when
they have time.
Events are hard to understand because there are three
different mechanism meshing.
- A callback delegate
mechanisms so that Sun-written Components can call
code you write when something interesting happens via the various listener
interfaces. The listener mechanism is more complex
than your usual callback. The callee, remembers multiple callers, then calls
them back later, asynchronously when something interesting happens.
- A queuing mechanism to procrastinate work. Basically it is a to-do list. High
priority work gets done first. If the system gets behind, it can skip some of
the validates or repaints since the later ones will cover the earlier ones. The
queue mechanism also co-ordinates many Threads all
making changes to the screen at once. They put their requests into a queue and a
single Thread does all the work without any worries
of other Threads interfering.
- It is also an Event delivery mechanism so that an Event
(notification of something interesting happening) can be easily forwarded to
other possibly interested parties.
Instead of Events percolating up to parents as they
did in JDK 1.0.2, any object or component can
register itself as a listener, interested in hearing about a type of Events
originating in some other component. When the Event
arises, the source component processes the Event
by dispatching it to each of the registered listeners.
Dispatching is synchronous, i.e. the listener handler routines are called
directly while the calling dispatcher waits for the handler to complete.
According to the specification, the listeners may be informed in any order, but
the usual implementation is a queue, with listeners informed in the same order
they were added. If you want to be absolutely sure of a fixed order of handling
listeners, make your own chain and pass the Event
between them. listeners pretty much only hear Events
they are interested in. They are not responsible for passing the Event
on to anyone else.
The new Event model gives you three places to hook in
your code to process Events.
- via listeners. This is by far the most common way and the easiest to set up. The
listener can be any object, e.g. the enclosing frame, the component itself, a
non-GUI code object, a "mom" object to mother a set of components. A
given listener can service several sources, and a given source can notify
several target listeners. listener methods have names like actionPerformed,
windowClosed, mouseMoved. Listeners are often implemented with Adapter
classes so you don’t have to write dummy methods for Events
you are not interested in. Adapter classes have names like: java.awt.event.FocusAdapter,
KeyAdapter and WindowAdapter. Note, there is
no such thing as ActionAdapter since it has only one
method. Often you use inner classes or anonymous classes to handle fielding Events
by extending an adapter class. Be very careful in extending adapter classes to
get your method names and signatures exactly right. Otherwise you won’t
override the corresponding dummy method, and your code won’t ever be
executed, leaving you wondering where all the Events
went.
- via processEvent which receives all raw Events
for the component. It does not see Events coming in
via listeners. If you override it, be sure to call super.processEvent
to ensure all the finer Event handlers, like processKeyEvent,
ProcessActionEvent etc. also get called and the listeners alerted. If
you attempt to restrict the Events coming to processEvent
with enableEvents(mask), if there are listeners
registered for Events not in the mask, those Event
classes will arrive anyway.
- via processXXXX(SomeEventClass e), e.g. processActionEvent,
processItemEvent. Every raw Event for the
component of that class comes through here. It does not see Events
coming in via listeners. Be sure to call super.processXXXX
to ensure the listeners are alerted.
A component might quite reasonably register itself as a listener for Events
arising in itself or for Events arising from the
associated native GUI. There is another deprecated technique to process Events
arising in itself. A component could override its processEvent routine, do some
action, then call super.processEvent (which would
inform all the listeners), then do some other action.
The Cast of Players
Methods for registering a target as a listener of Events
arising in some source include: source.addActionListener(target),
addAdjustmentListener, addComponentListener, addContainerListener,
addFocusListener, addItemListener, addKeyListener, addMouseListener,
addMouseMotionListener, addPropertyChangeListener, addTextListener,
addVetoableChangeListener, addWindowListener… Normally you would
invoke these methods but not override them.
The AWT informs the list of registered listeners that an Event
has occurred by calling the generic source.processEvent
which in turn calls the more specific Event
handler: source.processActionEvent,
processAdjustmentEvent, processComponentEvent, processContainerEvent,
processItemEvent, processKeyEvent, processMouseEvent, processMouseMotionEvent,
firePropertyChange, processTextEvent, fireVetoableChange, processWindowEvent…
These routines would in turn use the AWTEventMulticaster
to inform all the listeners and invoke their actionPerformed,
keyPressed, windowOpened etc. methods in turn. These processXXX routines
typically work by calling the relevant dispatching routines on the listener
targets. It is unlikely you will ever deal with these methods directly.
Your listener object must implement one or more interfaces
such as: ActionListener, ComponentListener,
ContainerListener, FocusListener, KeyListener, ItemListener, MouseListener,
MouseMotionListener, TextListener, WindowListener…
The easiest way to implement those interfaces is to extend one of the related adapter
classes. The adapter classes provide dummy methods to deal with the
subcategories of Events that are of no interest to
you. ComponentAdapter, ContainerAdapter, FocusAdapter,
KeyListener, ItemAdapter, MouseAdapter, MouseMotionAdapter, TextAdapter,
WindowAdapter…
The Event is dispatched to each target Listener
by invoking its listening method. These have names
like: target.actionPerformed, adjustmentValueChanged,
componentHidden, componentMoved, componentResized, componentShown,
componentAdded, componentRemoved, focusGained, focusLost, itemStateChanged,
keyPressed, keyReleased, keyTyped, mouseClicked, mouseEntered, mouseExited,
mousePressed, mouseReleased, textValueChanged, windowActivated, windowClosed,
windowClosing, windowDeactivated, windowDecionified, WindowIconified,
windowOpened… You need to write custom versions of these routines.
If there is a listener for an Event type registered,
then Events from the GUI for that Event
type will be delivered to that component’s processEvent.
The component can control which additional types are delivered to processEvent
by calling enableEvents with a mask. This way events
that no one is interested in are not even delivered to processEvent.
It is unlikely you will be directly involved with enableEvents.
A key point to understand is that a component has two kinds of acceptor routines
for Events.
- The generic processEvent routine primarily accepts Events
indirectly coming from the native GUI. Application programmers do not normally
use these methods.
- The listener methods, e.g. actionPerformed, accept Events
from other components. This is the main tool for application programmers to deal
with Events. Component writers can also use the EventListener
interface by registering a custom component as a listener to Events
arising in itself.
The Life Cycle of An Event
Where do Events Come
From Daddy?
You synthesize an AWTEvent and post to the SystemEventQueue
for later delivery.
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent( Event e );
In this Alice in Wonderland world, you set the source
field of the Event to the component or peer where you
want the Event delivered.
Later, when the source delivers the Event to the
various listeners, the term source for this field will
make more sense. A potentially separate thread will service the queue
asynchronously. When the Event percolates to the head
of the queue, the dispatcher will call event.getSource().processEvent(event);
Most Events arise in the native GUI. They can enter
the Java system in one of two ways:
- Notification of interesting happenings in the GUI arrive as a single stream of
messages mentioning native GUI components. This is the traditional native GUI Event
loop handled by hidden code, that calls getMessage()
in a loop. The AWT uses a hash table to convert references to native GUI
components into the corresponding Java components and peers, and composes
corresponding Java Events and enqueues them in the SystemEventQueue.
- Java peer components generate Events based on
information they glean from the native GUI. These too are enqueued in the SystemEventQueue.
Lightweight components generate Events and post them
directly to themselves, without going through the SystemEventQueue.
Where is the Event Loop?
People who cut their teeth in the Windows or Mac native low level GUI API are
used to writing application-specific code to handle dispatching using an Event
processing loop. The AWT handles this all automatically and invisibly in EventDispatchThread.run.
It reads the stream of native GUI Event messages, and
translates them, creating Java Events, which it puts
into the SystemEventQueue. The thread that processes Events,
when it finishing processing one Event, goes and
picks up another Event off the head of the Event
queue to dispatch. This is the very thread that executes most of your
application code.
The loop that processes Events does not even start
until your main program has finished executing!! This Event
loop code is not visible to the application programmer.
You might naively imagine there is some sort of mail delivery service that
delivers Events to their destinations. It is much
simpler than that. The Event object contains a
reference to the target object. To deliver/dispatch the Event,
all the system has to do is call a method on that reference. It is a little more
complicated than that since an Event handler may call
a chain of Event handlers. The target object is
confusingly called the source. It makes sense if you
consider that Events usually come back to the same
object that generated them. From the point of view of listeners, the Events
appear to come from these source objects.
You can think of Events as a way of enqueing work to
be done later, usually by other objects.
Creation
Consider what happens behind the scenes when the user clicks an OK button on the
screen. The underlying native GUI notices the mouse click.
It could then work one of two ways:
- The native GUI indirectly notifies the Java Button peer object which creates a
new Event using the ActionEvent
constructor.
- The GUI creates a native format message that a button has been clicked. Hidden
code in the AWT reads this stream of messages and translates this one into a new Event
using the ActionEvent constructor. It has a Hashtable
to convert references to native peer components to the corresponding Java
components.
-
- event source
- A Java component closely associated with this event, namely myButton.
At first this field acts as the destination for the Event.
Later it informs listeners where the Event came from.
- event id
- the type of event, in this case ActionEvent.ACTION_PERFORMED.
- event command
- the command string for this action event. In this case it would be the
string "OK".
- event modifiers
- the modifiers held down during this click, e.g. CTRL_MASK if the Ctrl key is
held down
Enqueuing
The newly created Event is then enqueued on the
system Event queue using:
EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue();
Now it can enqueue the Event for delivery to the
source ( myButton) with the system’s postEvent
method. We don’t use any methods of myButton just yet.
q.postEvent( e );
The Event hibernates in the queue waiting its turn to
be dispatched. postEvent will return immediately. It
won’t wait until the new Event has been
delivered.
Dispatching
There is a single system thread that grabs the next item off the queue and
dispatches it. You can see the code for it in EventDispatchThread.run
When all the processing for that Event is complete,
it gets the next Event. No other Event
processing, not even a mouse move, can happen until the previous Event
has been processed. I repeat, only one Event is
processed at a time.
Having said that, this single-thread behaviour is not part of the specification.
There are some JVMs where you have several threads processing Events
simultaneously.
It looks at the source field in the Event object, and
delivers the Event to that source object.
First it checks the java.awt.Component.eventMask to
be sure the myButton component is willing to accept that flavour of Event.
The button could conceivably have used java.awt.Component.disableEvents
to filter out Events such as ours. disableEvents
takes bit masks for groups of Events such as java.awt.AWTEvent.MOUSE_MOTION_EVENT_MASK.
You cannot feed it individual Events such as java.awt.event.MouseEvent.MOUSE_ENTERED,
because these constants are not in bit mask form. One my main bitches about Java
is this bit map / enumeration ambiguity.
It also checks if there are listeners for our type
of Event. If there are, even if our Event
type is blocked, our Event will still be delivered.
It delivers the Event to the button component by
calling myButton’s generic processEvent Event
handler:
myButton.processEvent( e );
or more precisely:
e.getSource().processEvent( e );
This dispatching mechanism is efficient because it does not require any sort of
table lookup or nested switch statements.
Classification
processEvent could in theory do some special
processing on the Event, but usually all it does is
classify the Event and call a more specialised Event
handler such as: processActionEvent. (If you
override processEvent, make sure you call super.processEvent
to ensure the standard Event processing also occurs.)
Had we been processing a mouse Event, we would have
called processMouseEvent instead.
Notifying Listeners
A long time ago, before this Event was born, parties
interested in this type of Event registered their
interest by using myButton. addActionListener(myListener).
It is important to remember that addXXXXListener
methods are implemented by the sources, not the
listeners. In theory, the addXXXXListener methods
could be invoked by the source or the listener, but usually they are invoked by
a third party, usually an enclosing frame.
Raw Events arrive via processEvent.
They are classified and redirected to more specialised Event
processors like processActionEvent.
processActionEvent can further classify the incoming Events
and can do whatever special processing it wants. Then it notifies any listeners,
one after the other. (If you override processActionEvent,
make sure you call super.processActionEvent to
ensure the listeners are notified.)
Each listener effectively gets a fresh cloned copy of the original Event.
Unfortunately, this wastes cpu cycles and creates a ton of run time garbage. The
intent is to make Events effectively read-only and to
render harmless any malicious component that stored a reference to an Event.
Once an Event enters Component.processKeyEvent,
it isn’t replicated anymore so presumably some of its fields could be
modified and used for communication. Ideally Event
objects would be read-only and recyclable.
There is no guarantee that the listeners will be notified in any particular
order, however they will be notified one at a time, never in parallel.
To notify each registered listener, the speaker’s processActionEvent
calls each listener’s actionPerformed(e)
method by chasing the AWTEventMultiCaster listener
chain.
AWTEventMulticaster contains generic code that most
components use for implementing addXXXListener and
the notification of listeners. Component has various
add/remove listener methods that use AWTEventMulticaster
that all Components inherit.
If we had been processing a mouse motion, we would have called the listener’s
mouseMoved method instead. processActionEvent
methods are designed to be called by processEvent;
methods like actionPerformed and mouseMoved
are part of listener interfaces. Most application coding uses only listeners.
Only if you were inventing a new sort of component would you get involved with processEvent
and processActionEvent.
What Are Listeners?
A Listener is any object that:
- implements one of the EventListener interfaces
such as ActionListener, e. g. an ActionListener
must implement an actionPerformed method to be
called when an Action Event
occurs.
- Has hooked itself (or been hooked by a third party) onto one or more Event-generating
components with a method such as addActionListener.
Listeners are written mostly by application programmers to field notifications
of interesting things that happen to Sun-written widgets such as JButtons.
A listener might be a component, an ordinary object, a special Mom object to
mother a set of components or an anonymous object created from an anonymous
class to simply provide a hook to some other routine.
A listener might be created by extending one of the adapter
shortcut classes. The adapter classes give you some dummy classes to start with
in defining a listener where you only want to handle a few types of Event.
A component may even register itself as a listener for Events
originating it itself. This is the preferred method for application programmers
since there is less possibility of mucking up standard Event
processing.
A listener can field Events from many different
sources. Similarly a source may notify many different listeners. Alternatively
you could have a separate listener object for every source, or anything in
between.
Components like JButtons implement interfaces like ItemSelectable.
The provide methods like addItemListener so that
classes that implement the ItemListener interface
can arrange to be notified of interesting events in the button, such as it being
pressed by first hooking themselves up through JButton.addItemListener.
The JButton uses EventListenList
to maintain the list of interested listeners. When something interesting happen
its calls fireActionPerformed which calls each of
the actionPerformed methods of the register
listeners. Even though an event gets passed to the actionPerformed
method, the listener mechanism does not necessarily mean the event spent any
time in a queue. It could be created and passed immediately to the listeners
when the interesting event happened.
You could even use the listener mechanism in your own code, for both the
notifier and listener end, for situations where it is hard to keep track of who
should notify whom when some interesting thing happens. You don’t need to
modify the callee’s code to add more interested parties. You don’t
need an event queue.
You can also set up a listener to monitor all the events. You might use this to
monitor keyboard or mouse activity for a timeout to shutdown after a period of
inactivity.
Listener Actions
What does the listener do when it’s actionPerformed
(or other mouseMoved etc…) method is called?
It may classify the incoming Event based on source,
id or class (via instanceof). Then it would perform some action, do a
calculation, change the look of the screen, delete a file… If it wants,
it can use the consume() method to mark the Event
consumedto discourage further processing on it.
It had better be quick! The entire system
is at a standstill, unable to process any more Events
until your listener completes its action. If the action is going to take more
than a few milliseconds you should either:
- Spin off a separate thread to do the work and immediately return from the Event
handler. With Swing you must be careful since Swing components are not thread
safe. You must only access them with the original thread-dispatching thread.
- Do a little of the work, and enqueue a synthetic Event
to remind yourself to do a little more of the work later.
- Create a second thread to dispatch Events, popping
them off the SystemEventQueue.
In a similar vein, if it is very important that some action not start until
after the physical paint is finished, the simplest way to arrange that is to
have paint kick it off, perhaps by examining some
flag to see if kickoff is necessary.
How Does consume() Work?
I have not found a precise definition of what is supposed to happen to a
consumed Event. By experiment, I discovered it seems
to stop further processing by the base classes of the current component. It does
not seem to stop notification of others listeners. I found for example that in TextField,
consuming a keystroke Event would prevent it being
displayed. However, this does not work with the KL group JCTextField
or the Swing JTextField. There you must override
processKeyEvent.
After all the listeners have been called, the Event
is checked to see if it has been consumed. If so, then it is not passed onto the
next stage. For e.g. KeyEvents for a TextArea,
this means that it is not passed back to the peer, and so does not reach the
underlying window/widget, and so does not appear.
consume() in KeyListeners
is effectively broken in Swing 0.7 since it is ignored. It looks like the Event
is drawn in JTextArea before being given to the
listeners.
For TextFields, you can change the value of the
keystroke in the Event in a KeyListener
before passing it on, e.g. convert it to lower or upper case. However under
Motif, converting to lower case is broken, and the entire technique is broken
for Swing. You would have to trap processKeyEvent
instead. In a TextField in the AWT, you get the Event
before the character has been painted on the screen. Therefore if you do a getText
or setText in your Event
handler, very likely will thoroughly confuse the AWT. The AWT was not designed
to do anything useful, just to look good on paper. To save your sanity, it is
perhaps best to look on the AWT as an mystery/adventure game. Your task is to
figure out by experiment what the methods do. Their names give you the barest of
clues. You then McGuyver-like must cobble the odd bits together to create
something practical.
How Do You Compose a Listener?
Java 1.1 added inner classes and anonymous classes to make it easier to hook up
listener code. Here is code that could appear in the middle of a frame
initialisation method that:
- defines a new anonymous class that extends the WindowAdapter
listener.
- defines a method in that class to deal with window closing Events.
- allocates an object of that class.
- hooks that object up as a listener to the current frame component.
- throws away the reference to the class. (The object won’t die since it is
on the list of listeners.)
Cremation
The various listeners eventually complete their work and return to actionPerformed.
The return stack unwinds: actionPerformed returns. processActionEvent
returns. processEvent returns. We are now back at
system the code that dispatches Events from the queue.
Our Event is forgotten and the next Event
takes the stage. With no more references to our old Event,
garbage collection soon recycles the RAM that the Event
occupied.
Table of Event Classes and Methods
Now we need to generalise from those specific examples.
Instead of creating an ActionEvent with id ActionEvent.ACTION_PERFORMED,
that is dispatched to processEvent, then passed on
to processActionEvent, then passed on to an ActionListener
(that registered its interest via addActionListener)
via its actionPerformed method, there are many other
possibilities you can generate from the following table:
| When |
Event Class |
id |
Specific
Event
Handler |
Listener
Interface |
Listener
Notification
Method |
Listener
registration |
Adapter shortcut |
Typical Components |
| Button clicked, MenuItem clicked, but not Choice selected. |
ActionEvent |
ACTION_PERFORMED |
processActionEvent |
ActionListener |
actionPerformed |
addActionListener |
|
Button, JButton, MenuItem,
JMenuItem |
| Slider moved |
AdjustmentEvent |
ADJUSTMENT_VALUE_CHANGED |
processAdjustmentEvent |
AdjustmentListener
ScrollPane |
adjustmentValueChanged |
addAdjustmentListener |
|
JSlider |
| Component hidden, moved, resized, revealed. |
ComponentEvent |
COMPONENT_HIDDEN
COMPONENT_MOVED
COMPONENT_RESIZED
COMPONENT_SHOWN |
processComponentEvent |
ComponentListener |
componentHidden
componentMoved
componentResized
componentShown |
addComponentListener |
ComponentAdapter |
Component |
| something added or removed to this container. |
ContainerEvent |
COMPONENT_ADDED
COMPONENT_REMOVED |
processContainerEvent |
ContainerListener |
componentAdded
componentRemoved |
addContainerListener |
ContainerAdapter |
Container, Panel, Jpanel,
Frame, JFrame |
| Focus changed, either by mouse or tab |
FocusEvent |
FOCUS_GAINED
FOCUS_LOST |
processFocusEvent |
FocusListener |
focusGained
focusLost |
addFocusListener |
FocusAdapter |
Component |
| Ancestor added or removed |
HierarchyEvent |
HIERARCHY_CHANGED |
processHierarchyEvent |
HierarchyListener |
hierarchyChanged |
addHierarchyListener |
|
Component |
| Ancestor moved or resized |
HierarchyBoundsListener |
ANCESTOR_MOVED ANCESTOR_RESIZED |
processsHierarchyBoundsEvent |
HierarchyBoundsListener |
ancestorMoved
ancestorResized |
addHierarchyBoundsListener |
|
Component |
| user keyed something into a text box |
InputMethodEvent |
INPUT_METHOD_TEXT_CHANGED
CARET_POSITION_CHANGED |
processInputMethodEvent |
InputMethodListener |
inputMethodTextChanged
caretPositionChanged |
addInputMethodListener |
|
Component |
| user selected a different Choice |
ItemEvent |
DESELECTED
ITEM_STATE_CHANGED
SELECTED |
processItemEvent |
ItemListener |
itemStateChanged |
addItemListener |
|
Choice, JComboBox |
| keystroke |
KeyEvent |
KEY_PRESSED (e.g. shift, F1)
KEY_RELEASED
KEY_TYPED (e.g. a A) |
processKeyEvent |
KeyListener |
keyPressed
keyReleased
keyTyped |
addKeyListener |
KeyAdapter |
Panel, JPanel, Frame,
JFrame
not Button, JButton,
TextArea, JTextArea, Choice,
ComboBox, Checkbox, JCheckbox |
| Mouse clicked, entered or exited the region. See also ActionEvent and Item
event. |
MouseEvent |
MOUSE_CLICKED
MOUSE_ENTERED
MOUSE_EXITED
MOUSE_PRESSED
MOUSE_RELEASED |
processMouseEvent |
MouseListener |
mouseClicked
mouseEntered
mouseExited
mousePressed
mouseReleased |
addMouseListener |
MouseAdapter |
Component, Canvas, Panel,
JPanel,
not Button, JButton,
TextArea, JTextArea, Choice,
ComboBox, Checkbox, JCheckbox |
| Mouse moved. You normally don’t want to track these since there are so
many of them. |
MouseMotionEvent |
MOUSE_DRAGGED
MOUSE_MOVED |
processMouseMotionEvent |
MouseMotionListener |
mouseDragged
mouseMoved |
addMouseMotionListener |
MouseMotionAdapter |
Component, Canvas, Panel,
JPanel,
not Button, JButton,
TextArea, JTextArea, Choice,
ComboBox, Checkbox, JCheckbox |
| Mousewheel moved. |
MouseWheelEvent |
|
processMouseWheelEvent |
MouseWheelListener |
mouseWheelMoved |
addMouseWheelListener |
|
Component
Canvas
JPanel |
| repaint |
PaintEvent |
PAINT
UPDATE |
paint
update |
|
|
|
|
Component |
| value in a TextField changed. |
TextEvent |
TEXT_VALUE_CHANGED |
processTextEvent |
TextListener |
textValueChanged |
addTextListener |
|
TextField, TextArea, JTextField,
JTextArea |
| Window state change |
WindowEvent |
WINDOW_ACTIVATED
WINDOW_CLOSED
WINDOW_CLOSING
WINDOW_DEACTIVATED
WINDOW_DEICONIFIED
WINDOW_ICONIFIED
WINDOW_OPENED |
processWindowEvent |
WindowListener |
windowActivated
windowClosed
windowClosing
windowDeactivated
windowDecionified
WindowIconified
windowOpened |
addWindowListener |
WindowAdapter |
Frame, JFrame, Window,
JWindow, Dialog, JDialog |
For a complete list of the various Listener interfaces, and methods see the JDK API/Package-java.awt.event.html
if you want to study the whole java.awt.event
package.
Censoring or Remapping Keystrokes
Let us say you wanted to write your own version of TextField-like
component that disallowed certain keystrokes, or that processed certain
keystrokes specially. You may have tried the easy Listener/consume()
method and discovered it did not work for your base component. Here is bigger
hammer to use:
If you want the underlying base class not to see the Event,
you consume it in one of your custom filter routines such as unicodeKeyPressed(e),
and that suppresses the call to super.processKeyEvent.
Something to baffle you. Hitting the Enter key under NT generates two keystrokes
a \r and a \n. On other platforms you get just a \n. Best just to look for
VK_ENTER.
In Swing you can use the Keymap and KeyStroke
classes to control the actions of various keystrokes.
Activating
To activate your Listener, you need to create a callback delegate Listener
object, and pass it to the Component. Then when
something interesting happens, it can call one of the methods you wrote (to its
interface specifications), perhaps indirectly via the Event
queue.
Styles of Activating
There are several styles for activating:
compact
You do it all in one very complicated line, where you define an anonymous class,
create an instance of it and feed it to the addListener method. The puts
everything in one place. The code is an unreadable forest of parentheses braces
and semicolons. They limit you to one component per listener. They generate one
extra inner class per use. They don’t have to deal with the problem of
figuring out which component the incoming event to the listener is relevant too.
There is only one possibility. Java beautifiers
tend to over-indent this style. Inner classes get total and easy access to all
the mother class’s fields. The examples that follow are all of this style.
anonymous class reference
You create a reference to a anonymous inner class object then in a separate step,
feed that to the addListener method. I prefer this to (1) because it is easier
to proofread and SlickEdit beautifier renders it without excessive indentation.
You have the option of reusing a Listener on several components. You can also
collect all your listener logic in one place separate from the GUI logic which
makes it easier to maintain. This is the style I most commonly use personally.
named inner class reference
Like (2), but you name the inner classes. Perhaps a little easier for novices to
understand.
named free standing classes
like (3) but with ordinary classes that implement the Listener interfaces. They
don’t have easy access to the parent class variable. You can use designs
where the mother class itself implements the Listener interface, or where the
Listener classes have additional functionality. The Listener classes can have
parameters in their constructors, which anonymous inner classes cannot. This is
the most flexible approach, but also requires the most typing.
Stomp Style
These are mechanically generated. One master hookListeners
method calls a separate method to hook up each component. Those methods in turn
build an anonymous listener and link it to the widget. On activation they call
one line of code, which is yet another method that actually does the work. It is
verbose, but easy to proofread because the important stuff is separated from the
bubblegum. Here is what you type:
I would be happy to share my Stomp cookie cutter
classes for you.
Activating a Button
Hooking up some code to be executed when a button is hit is very similar to the
earlier WindowClosing example. There is no such thing
as the ActionAdapter class, so we use the name of the
interface instead.
Activating a FocusListener
Activating a KeyListener
Activating a Choice or JCheckbox ItemListener
JMenuItems
Just a few simple menus will explode into gobs of code if you provide an
anonymous ActionListener for each JMenuItem.
Instead, use a common ActionListener for all JMenuItems
or all JMenuItems in a JMenu.
Give each JMenuItem a one-letter setActionCommand
string. In your ActionListener, extract the
command letter with getActionCommand().charAt(0) and
use it to index a switch to select the piece of
code that needs to execute. Remember to add your ActionListener
to every individual JMenuItem. Best look into Actions
for hooking up your JButtons and JMenuItems
if you have more than a handful.
TextListener.textValueChanged
This method receives an event any time the text value of the component changes including
when you change it programmatically with setText!
This can cause your program to go into heart fibrillation, if your event handler
does any setTexts that in turn will trigger further Events
in a Disney-style ping-pong-balls-on-the-mousetraps fission-style chain reaction.
To add to the confusion, some versions of the JDK don’t do this.
Missing Events
The newsgroups are full of plaintive messages from newbies claiming the AWT is
broken because their listeners are not receiving any Events.
Here are some things to check:
- When you override a listener method of an Adapter, did you spell the name of the
listener method correctly with exactly the right signature? If you did not, the
compiler will give no error message and all the Events
will continue to be delivered to the Adapter’s dummy listener method. Be
especially careful when you are experimenting with a variety of places to tap in.
You must change the type of the Event parameter.
- Are any Events arriving? Perhaps the Events
arriving are slightly different from those expected. Put in some debug code to
dump out whatever is arriving to any of the listener methods.
- Are you sure your component is supposed to be generating the events you expect?
Often components don’t pass on low-level mouseMoved
or mouseClicked events, but instead pass on higher
level ItemStateChanged or actionPerformed
events.
- Are you sure you remembered to hook up your component to your listener with theComponent.addXXXListener(theListener)?
Did you use the right add method?
- You will never get any keystrokes out of a component that does not have focus.
- Don’t expect keystroke events to appear at a Panel.
They will appear at the component in the Panel that
has focus.
- Don’t expect keystrokes from a ListBox. You will
get ItemStateChanged events instead.
Mouse Events
There are three different listeners for the mouse, MouseListener,
MouseMotionListener and MouseWheelListener.
If you click the mouse, you will get three events which will show up at mousePressed,
then mouseReleased then mouseClicked.
If you move the mouse onto your component you will first get an event at mouseEntered.
If you move if off the component, you will get an event at mouseExited.
Every time you move the mouse even a tad, you will get an event at mouseMoved.
If you press a mouse button and drag you will get an event at mousePressed
followed by multiple events at mouseDragged
reporting the current drag location. When you let go of the button, you get an
event at mouseReleased. You won’t get an event
at mouseClicked
If you twirl the wheelmouse on a Component in a ScrollPane,
you won’t see events at the Component’s mouseWheelmoved.
Instead you will see events at mouseMoved, as if
there were no ScrollPane and user had moved the mouse
instead. Events are mouseMoved are about relative
motion over the virtual panned Component in the ScrollPane.
Detecting Shift, Alt and Ctrl, Left and Right Mouse
Buttons
When the user hits Shift, Alt or Ctrl (or a combination) in conjunction with
some ordinary key, you can determine that by looking at the KeyEvent.getModifiers,
masking with ORed combinations of inputEvent.SHIFT_MASK,
ALT_MASK, CTRL_MASK and META_MASK, in your keyTyped
listener.
You can detect the separate pressing of Shift, Alt or Ctrl at the keyPressed()
listener, and the releasing at the keyReleased
listener, by testing KeyEvent.getKeyCode() for a match with KeyEvent.VK_SHIFT,
VK_CONTROL, VK_ALT and KeyEvent.VK_META.
With the advent of JDK 1.3, you can detect and set
the state of the various CapsLock-like states with: public
boolean getLockingKeyState(int keyCode) and public
void setLockingKeyState(int keyCode, boolean on). Valid key codes are: VK_CAPS_LOCK,
VK_NUM_LOCK, VK_SCROLL_LOCK, VK_KANA_LOCK.
Dealing with mouse clicks is similar. In your mouseClicked
listener, MouseEvent.getModifiers() returns the state
of all the modifier keys and mouse buttons when the event was fired. You can use
this method to determine which mouse button was pressed (or newly released) when
a mouse Event was fired. You mask the result with an
ORed combination of InputEvent.ALT_MASK, BUTTON1_MASK,
BUTTON2__MASK, BUTTON3_MASK, CTRL_MASK, META_MASK, and SHIFT_MASK.
For example, the following expression is true if the right button was pressed:
The various combinations of ID, keyCode()
and keyChar() in the KeyEvent
are quite complicated.
getID() tells you the type of event, basically which
listener was used. KeyCode() gets you a raw Keyboard
code e.g. "A". keyChar() gets you the
cooked character e.g. "a". See KeyListener
in the Java glossary for more discussion.
I suggest downloading my little KeyPlay
application. You can play with it, clicking the mouse and hitting keystrokes.
A description of the Events generated is dumped to
the console. With it, you can quickly learn about the ordering of Events,
and the use of the fields. You will discover most JVMs work the same way, with
the exception of Microsoft’s which is out in left field. Check out Sun’s
java.awt. Robot class
for generating simulated keystrokes and mouse moves.
Keystroke Events
Whenever you hit a key on the keyboard, three events will show up at your KeyListener,
first an event at keyPressed, then at keyTyped,
then at keyReleased. If you hold a key down and it
repeats, you will see that triple repeated over and over. If you hit the ctrl
key and hold it down you will see repeated events at keyPressed
and finally one at keyReleased when you let go.
There is no event at keyTyped. When you hit Ctrl+C
you see keyCode 03, ^C, ETX not the letter C. Java combines the keys for you. It
is not your problem to track the ctrl or shift state and modify the meanings of
other keystrokes.
Keystroke Names
You should write your code using these KeyChar and KeyCode
names from java.awt.event.KeyEvent rather than hard-coding
the numeric literals.
| Numeric Ordering |
| name |
decimal |
hex |
| VK_UNDEFINED |
0 |
0x0 |
| CHAR_UNDEFINED |
0 |
0x0 |
| VK_CANCEL |
3 |
0x3 |
| VK_BACK_SPACE (\b) |
8 |
0x8 |
| VK_TAB (\t) |
9 |
0x9 |
| VK_ENTER (\n) |
10 |
0xa |
| VK_CLEAR |
12 |
0xc |
| VK_SHIFT |
16 |
0x10 |
| VK_CONTROL |
17 |
0x11 |
| VK_ALT |
18 |
0x12 |
| VK_PAUSE |
19 |
0x13 |
| VK_CAPS_LOCK |
20 |
0x14 |
| VK_KANA |
21 |
0x15 |
| VK_FINAL |
24 |
0x18 |
| VK_KANJI |
25 |
0x19 |
| VK_ESCAPE |
27 |
0x1b |
| VK_CONVERT |
28 |
0x1c |
| VK_NONCONVERT |
29 |
0x1d |
| VK_ACCEPT |
30 |
0x1e |
| VK_MODECHANGE |
31 |
0x1f |
| VK_SPACE |
32 |
0x20 |
| VK_PAGE_UP |
33 |
0x21 |
| VK_PAGE_DOWN |
34 |
0x22 |
| VK_END |
35 |
0x23 |
| VK_HOME |
36 |
0x24 |
| VK_LEFT |
37 |
0x25 |
| VK_UP |
38 |
0x26 |
| VK_RIGHT |
39 |
0x27 |
| VK_DOWN |
40 |
0x28 |
| VK_COMMA |
44 |
0x2c |
| VK_PERIOD |
46 |
0x2e |
| VK_SLASH |
47 |
0x2f |
| VK_0 |
48 |
0x30 |
| VK_1 |
49 |
0x31 |
| VK_2 |
50 |
0x32 |
| VK_3 |
51 |
0x33 |
| VK_4 |
52 |
0x34 |
| VK_5 |
53 |
0x35 |
| VK_6 |
54 |
0x36 |
| VK_7 |
55 |
0x37 |
| VK_8 |
56 |
0x38 |
| VK_9 |
57 |
0x39 |
| VK_SEMICOLON |
59 |
0x3b |
| VK_EQUALS |
61 |
0x3d |
| VK_A |
65 |
0x41 |
| VK_B |
66 |
0x42 |
| VK_C |
67 |
0x43 |
| VK_D |
68 |
0x44 |
| VK_E |
69 |
0x45 |
| VK_F |
70 |
0x46 |
| VK_G |
71 |
0x47 |
| VK_H |
72 |
0x48 |
| VK_I |
73 |
0x49 |
| VK_J |
74 |
0x4a |
| VK_K |
75 |
0x4b |
| VK_L |
76 |
0x4c |
| VK_M |
77 |
0x4d |
| VK_N |
78 |
0x4e |
| VK_O |
79 |
0x4f |
| VK_P |
80 |
0x50 |
| VK_Q |
81 |
0x51 |
| VK_R |
82 |
0x52 |
| VK_S |
83 |
0x53 |
| VK_T |
84 |
0x54 |
| VK_U |
85 |
0x55 |
| VK_V |
86 |
0x56 |
| VK_W |
87 |
0x57 |
| VK_X |
88 |
0x58 |
| VK_Y |
89 |
0x59 |
| VK_Z |
90 |
0x5a |
| VK_OPEN_BRACKET |
91 |
0x5b |
| VK_BACK_SLASH |
92 |
0x5c |
| VK_CLOSE_BRACKET |
93 |
0x5d |
| VK_NUMPAD0 |
96 |
0x60 |
| VK_NUMPAD1 |
97 |
0x61 |
| VK_NUMPAD2 |
98 |
0x62 |
| VK_NUMPAD3 |
99 |
0x63 |
| VK_NUMPAD4 |
100 |
0x64 |
| VK_NUMPAD5 |
101 |
0x65 |
| VK_NUMPAD6 |
102 |
0x66 |
| VK_NUMPAD7 |
103 |
0x67 |
| VK_NUMPAD8 |
104 |
0x68 |
| VK_NUMPAD9 |
105 |
0x69 |
| VK_MULTIPLY |
106 |
0x6a |
| VK_ADD |
107 |
0x6b |
| VK_SEPARATER |
108 |
0x6c |
| VK_SUBTRACT |
109 |
0x6d |
| VK_DECIMAL |
110 |
0x6e |
| VK_DIVIDE |
111 |
0x6f |
| VK_F1 |
112 |
0x70 |
| VK_F2 |
113 |
0x71 |
| VK_F3 |
114 |
0x72 |
| VK_F4 |
115 |
0x73 |
| VK_F5 |
116 |
0x74 |
| VK_F6 |
117 |
0x75 |
| VK_F7 |
118 |
0x76 |
| VK_F8 |
119 |
0x77 |
| VK_F9 |
120 |
0x78 |
| VK_F10 |
121 |
0x79 |
| VK_F11 |
122 |
0x7a |
| VK_F12 |
123 |
0x7b |
| VK_DELETE (ASCII del) |
127 |
0x7f |
| VK_NUM_LOCK |
144 |
0x90 |
| VK_SCROLL_LOCK |
145 |
0x91 |
| VK_PRINTSCREEN |
154 |
0x9a |
| VK_INSERT |
155 |
0x9b |
| VK_HELP |
156 |
0x9c |
| VK_META |
157 |
0x9d |
| VK_BACK_QUOTE |
192 |
0xc0 |
| VK_QUOTE |
222 |
0xde |
|
| Alpha Ordering |
| name |
decimal |
hex |
| CHAR_UNDEFINED |
0 |
0x0 |
| VK_0 |
48 |
0x30 |
| VK_1 |
49 |
0x31 |
| VK_2 |
50 |
0x32 |
| VK_3 |
51 |
0x33 |
| VK_4 |
52 |
0x34 |
| VK_5 |
53 |
0x35 |
| VK_6 |
54 |
0x36 |
| VK_7 |
55 |
0x37 |
| VK_8 |
56 |
0x38 |
| VK_9 |
57 |
0x39 |
| VK_A |
65 |
0x41 |
| VK_ACCEPT |
30 |
0x1e |
| VK_ADD |
107 |
0x6b |
| VK_ALT |
18 |
0x12 |
| VK_B |
66 |
0x42 |
| VK_BACK_QUOTE |
192 |
0xc0 |
| VK_BACK_SLASH |
92 |
0x5c |
| VK_BACK_SPACE (\b) |
8 |
0x8 |
| VK_C |
67 |
0x43 |
| VK_CANCEL |
3 |
0x3 |
| VK_CAPS_LOCK |
20 |
0x14 |
| VK_CLEAR |
12 |
0xc |
| VK_CLOSE_BRACKET |
93 |
0x5d |
| VK_COMMA |
44 |
0x2c |
| VK_CONTROL |
17 |
0x11 |
| VK_CONVERT |
28 |
0x1c |
| VK_D |
68 |
0x44 |
| VK_DECIMAL |
110 |
0x6e |
| VK_DELETE (ASCII del) |
127 |
0x7f |
| VK_DIVIDE |
111 |
0x6f |
| VK_DOWN |
40 |
0x28 |
| VK_E |
69 |
0x45 |
| VK_END |
35 |
0x23 |
| VK_ENTER (\n) |
10 |
0xa |
| VK_EQUALS |
61 |
0x3d |
| VK_ESCAPE |
27 |
0x1b |
| VK_F |
70 |
0x46 |
| VK_F1 |
112 |
0x70 |
| VK_F10 |
121 |
0x79 |
| VK_F11 |
122 |
0x7a |
| VK_F12 |
123 |
0x7b |
| VK_F2 |
113 |
0x71 |
| VK_F3 |
114 |
0x72 |
| VK_F4 |
115 |
0x73 |
| VK_F5 |
116 |
0x74 |
| VK_F6 |
117 |
0x75 |
| VK_F7 |
118 |
0x76 |
| VK_F8 |
119 |
0x77 |
| VK_F9 |
120 |
0x78 |
| VK_FINAL |
24 |
0x18 |
| VK_G |
71 |
0x47 |
| VK_H |
72 |
0x48 |
| VK_HELP |
156 |
0x9c |
| VK_HOME |
36 |
0x24 |
| VK_I |
73 |
0x49 |
| VK_INSERT |
155 |
0x9b |
| VK_J |
74 |
0x4a |
| VK_K |
75 |
0x4b |
| VK_KANA |
21 |
0x15 |
| VK_KANJI |
25 |
0x19 |
| VK_L |
76 |
0x4c |
| VK_LEFT |
37 |
0x25 |
| VK_M |
77 |
0x4d |
| VK_META |
157 |
0x9d |
| VK_MODECHANGE |
31 |
0x1f |
| VK_MULTIPLY |
106 |
0x6a |
| VK_N |
78 |
0x4e |
| VK_NONCONVERT |
29 |
0x1d |
| VK_NUM_LOCK |
144 |
0x90 |
| VK_NUMPAD0 |
96 |
0x60 |
| VK_NUMPAD1 |
97 |
0x61 |
| VK_NUMPAD2 |
98 |
0x62 |
| VK_NUMPAD3 |
99 |
0x63 |
| VK_NUMPAD4 |
100 |
0x64 |
| VK_NUMPAD5 |
101 |
0x65 |
| VK_NUMPAD6 |
102 |
0x66 |
| VK_NUMPAD7 |
103 |
0x67 |
| VK_NUMPAD8 |
104 |
0x68 |
| VK_NUMPAD9 |
105 |
0x69 |
| VK_O |
79 |
0x4f |
| VK_OPEN_BRACKET |
91 |
0x5b |
| VK_P |
80 |
0x50 |
| VK_PAGE_DOWN |
34 |
0x22 |
| VK_PAGE_UP |
33 |
0x21 |
| VK_PAUSE |
19 |
0x13 |
| VK_PERIOD |
46 |
0x2e |
| VK_PRINTSCREEN |
154 |
0x9a |
| VK_Q |
81 |
0x51 |
| VK_QUOTE |
222 |
0xde |
| VK_R |
82 |
0x52 |
| VK_RIGHT |
39 |
0x27 |
| VK_S |
83 |
0x53 |
| VK_SCROLL_LOCK |
145 |
0x91 |
| VK_SEMICOLON |
59 |
0x3b |
| VK_SEPARATER |
108 |
0x6c |
| VK_SHIFT |
16 |
0x10 |
| VK_SLASH |
47 |
0x2f |
| VK_SPACE |
32 |
0x20 |
| VK_SUBTRACT |
109 |
0x6d |
| VK_T |
84 |
0x54 |
| VK_TAB (\t) |
9 |
0x9 |
| VK_U |
85 |
0x55 |
| VK_UNDEFINED |
0 |
0x0 |
| VK_UP |
38 |
0x26 |
| VK_V |
86 |
0x56 |
| VK_W |
87 |
0x57 |
| VK_X |
88 |
0x58 |
| VK_Y |
89 |
0x59 |
| VK_Z |
90 |
0x5a |
|
| Sun Ordering |
| name |
decimal |
hex |
| VK_ENTER (\n) |
10 |
0xa |
| VK_BACK_SPACE (\b) |
8 |
0x8 |
| VK_TAB (\t) |
9 |
0x9 |
| VK_CANCEL |
3 |
0x3 |
| VK_CLEAR |
12 |
0xc |
| VK_SHIFT |
16 |
0x10 |
| VK_CONTROL |
17 |
0x11 |
| VK_ALT |
18 |
0x12 |
| VK_PAUSE |
19 |
0x13 |
| VK_CAPS_LOCK |
20 |
0x14 |
| VK_ESCAPE |
27 |
0x1b |
| VK_SPACE |
32 |
0x20 |
| VK_PAGE_UP |
33 |
0x21 |
| VK_PAGE_DOWN |
34 |
0x22 |
| VK_END |
35 |
0x23 |
| VK_HOME |
36 |
0x24 |
| VK_LEFT |
37 |
0x25 |
| VK_UP |
38 |
0x26 |
| VK_RIGHT |
39 |
0x27 |
| VK_DOWN |
40 |
0x28 |
| VK_COMMA |
44 |
0x2c |
| VK_PERIOD |
46 |
0x2e |
| VK_SLASH |
47 |
0x2f |
| VK_0 |
48 |
0x30 |
| VK_1 |
49 |
0x31 |
| VK_2 |
50 |
0x32 |
| VK_3 |
51 |
0x33 |
| VK_4 |
52 |
0x34 |
| VK_5 |
53 |
0x35 |
| VK_6 |
54 |
0x36 |
| VK_7 |
55 |
0x37 |
| VK_8 |
56 |
0x38 |
| VK_9 |
57 |
0x39 |
| VK_SEMICOLON |
59 |
0x3b |
| VK_EQUALS |
61 |
0x3d |
| VK_A |
65 |
0x41 |
| VK_B |
66 |
0x42 |
| VK_C |
67 |
0x43 |
| VK_D |
68 |
0x44 |
| VK_E |
69 |
0x45 |
| VK_F |
70 |
0x46 |
| VK_G |
71 |
0x47 |
| VK_H |
72 |
0x48 |
| VK_I |
73 |
0x49 |
| VK_J |
74 |
0x4a |
| VK_K |
75 |
0x4b |
| VK_L |
76 |
0x4c |
| VK_M |
77 |
0x4d |
| VK_N |
78 |
0x4e |
| VK_O |
79 |
0x4f |
| VK_P |
80 |
0x50 |
| VK_Q |
81 |
0x51 |
| VK_R |
82 |
0x52 |
| VK_S |
83 |
0x53 |
| VK_T |
84 |
0x54 |
| VK_U |
85 |
0x55 |
| VK_V |
86 |
0x56 |
| VK_W |
87 |
0x57 |
| VK_X |
88 |
0x58 |
| VK_Y |
89 |
0x59 |
| VK_Z |
90 |
0x5a |
| VK_OPEN_BRACKET |
91 |
0x5b |
| VK_BACK_SLASH |
92 |
0x5c |
| VK_CLOSE_BRACKET |
93 |
0x5d |
| VK_NUMPAD0 |
96 |
0x60 |
| VK_NUMPAD1 |
97 |
0x61 |
| VK_NUMPAD2 |
98 |
0x62 |
| VK_NUMPAD3 |
99 |
0x63 |
| VK_NUMPAD4 |
100 |
0x64 |
| VK_NUMPAD5 |
101 |
0x65 |
| VK_NUMPAD6 |
102 |
0x66 |
| VK_NUMPAD7 |
103 |
0x67 |
| VK_NUMPAD8 |
104 |
0x68 |
| VK_NUMPAD9 |
105 |
0x69 |
| VK_MULTIPLY |
106 |
0x6a |
| VK_ADD |
107 |
0x6b |
| VK_SEPARATER |
108 |
0x6c |
| VK_SUBTRACT |
109 |
0x6d |
| VK_DECIMAL |
110 |
0x6e |
| VK_DIVIDE |
111 |
0x6f |
| VK_F1 |
112 |
0x70 |
| VK_F2 |
113 |
0x71 |
| VK_F3 |
114 |
0x72 |
| VK_F4 |
115 |
0x73 |
| VK_F5 |
116 |
0x74 |
| VK_F6 |
117 |
0x75 |
| VK_F7 |
118 |
0x76 |
| VK_F8 |
119 |
0x77 |
| VK_F9 |
120 |
0x78 |
| VK_F10 |
121 |
0x79 |
| VK_F11 |
122 |
0x7a |
| VK_F12 |
123 |
0x7b |
| VK_DELETE (ASCII del) |
127 |
0x7f |
| VK_NUM_LOCK |
144 |
0x90 |
| VK_SCROLL_LOCK |
145 |
0x91 |
| VK_PRINTSCREEN |
154 |
0x9a |
| VK_INSERT |
155 |
0x9b |
| VK_HELP |
156 |
0x9c |
| VK_META |
157 |
0x9d |
| VK_BACK_QUOTE |
192 |
0xc0 |
| VK_QUOTE |
222 |
0xde |
| VK_FINAL |
24 |
0x18 |
| VK_CONVERT |
28 |
0x1c |
| VK_NONCONVERT |
29 |
0x1d |
| VK_ACCEPT |
30 |
0x1e |
| VK_MODECHANGE |
31 |
0x1f |
| VK_KANA |
21 |
0x15 |
| VK_KANJI |
25 |
0x19 |
| VK_UNDEFINED |
0 |
0x0 |
| CHAR_UNDEFINED |
0 |
0x0 |
|
Event vs. AWTEvent
In JDK 1.0.2 there was basically only one kind of Event,
called the java.awt.Event. In JDK 1..6+
there is a base Event called a java.awt.event.AWTEvent.
All the other types of Event such as java.awt.event.actionEvent
and java.awt.event.WindowEvent are derived from it.
It is a bit confusing since the Event package for JDK 1..6+
is called java.awt.event instead of java.awt.AWTEvent
as you might expect.
Synthetic Events
There are a number of ways to fake an event.
- Check out Sun’s java.awt.Robot
class for generating simulated keystrokes and mouse
moves.
- The simplest is just to call a Listener method directly with a dummy Event
object, filled in with just enough data to keep the method happy.
- Create an Event and introduce it to the Component
that will handle it at the processEvent method. with:
- Create an Event and introduce it to the SystemEventQueue
with:
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent( Event e );
- Generating MouseMoved Events
has no effect on the screen mouse cursor. To make the underlying native GUI see
your generated Events, use the Robot
class to generate move clicks, moves etc.
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.Robot;
...
Robot r = new Robot();
r.mouseMove( 100, 100 );
r.keyPress( KeyEvent.VK_A );
r.mousePress( InputEvent.BUTTON1_MASK );
Changes from JDK 1.0
In addition to the complete revamping of how it works
inside, some of the interfacing details have changed too. The names of the
various Events no longer live in the Event
class. They live in the various subclasses of AWTEvent
e.g. WindowEvent.WINDOW_CLOSED. Some Events
have been renamed. WINDOW_DESTROY is now WINDOW_CLOSED.
You can no longer access the source and id
fields of an Event directly. You must call getSource
and getID. handleEvent
is now called processEvent. See my essay on JDK
1.0 events, primarily now of historical
interest.
Changes with JDK 1.3
Starting with JDK 1.3 it is
possible to insert custom Events into the system Event
queue with awt.EventQueue.invokeLater( Runnable r )
(or invokeAndWait). It also appears to be possible
to take direct control of the Event queue by sub-classing
it using getToolkit().getSystemEventQueue().push( subclass
of awt.EventQueue). You could then add some monitoring debug code for
example.
The catch is MouseEvents you create this way don’t
move the mouse cursor. To insert native events that will, see the Robot
class. It can also be used to generate synthetic keystroke events. The Robot
class helps you create self running demos.
Repaint
You may have seen mention of PaintEvent.PAINT and PaintEvent.UPDATE
events. These are used to control how components are repainted. The repaint
mechanism partly uses the SystemEventQueue and partly
the queue inside the native GUI. See repaint in the
Java & Internet Glossary for details on how it works.
Learning More
Sun’s Javadoc on
EventQueue class : available:
Credits
This article would not have been possible without the patient assistance of Jan
Newmarch who researched the material.
 |
recommend book⇒Tricks of The Java Programing Gurus |
| | paperback |
|---|
| ISBN13: | 978-1-57521-102-2 |
|---|
| publisher: | Sams |
| published: | 1996-07 |
| by: | Glenn L. Vanderburg |
| Chapter by Jan Newmarch |
|
Richard
Baldwin and Peter Mehlitz helped explain the JDK 1.1+
event processing. Steve Odendahl pointed out the mismatched signature problem in
Adapters. Tuyen Tran pointed out the problems with Swing and threading.
Summary
The key point to understand is that a component has two kinds of acceptor
routines for Events. The generic processEvent routine
primarily accepts events coming indirectly from the native GUI which it then
fobs off on the more specific processXXXXEvent
methods. The listener methods, e.g. actionPerformed,
accept Events from other components. The listener
methods are primarily for application programmers and those who customise
existing components; the processXXXXEvent methods
are primarily for the writers of radically new components.