Concepts / ActionListener and Event Handling

ActionListener and Event Handling

A javax.swing.Timer exists to generate ActionEvent events at a fixed delay, and it can also be set to fire a single event as an alarm.

  • Programming

Motion Built from Still Pictures

Animation on a screen is an illusion. It is built from a simple ingredient: a series of still pictures shown one after another. When the pictures change quickly and only a little at a time, the eye reads the sequence as one moving object rather than a slideshow. A computer animation is exactly this kind of sequence of still images.

Java needs some way to decide when each new picture should appear. The tool it offers for this job is the javax.swing.Timer, an object that produces a steady beat of events. Each beat gives your program an opportunity to compute the next picture and draw it. This article starts from a Timer-driven animation and works backward to the mechanism that makes it move, so that when something goes wrong you know where to look.

What do you think happens?

You create a Timer and pass it a listener, but you never call start() on it. What happens to the listener's actionPerformed method?

Reveal answer
Answer:

actionPerformed is never invoked.

A Timer does not start on creation. Until start() is called, the Timer exists but is quiet, so it never announces a moment to the listener. Keep this in mind: the rest of the article shows why that single missing call stops the whole animation.

The Three States of a Timer

Creating a Timer and starting it are two separate steps. A timer does not automatically start generating events when the timer object is created. The start() method must be called to tell the timer to start running. The stop() method turns the stream of events off, and the timer can be restarted later by calling start() again.

A running timer can therefore move through a small cycle: created but idle, running, stopped, and running again. The diagram below traces which method call causes each change. Notice that the idle state and the stopped state are both quiet, since no events flow in either one; only the running state delivers the beat that drives an animation.

Idlecreated, no eventsRunningevents flowStoppedevent stream offstart()stop()start()
What state is the Timer in before start(), while running, and after stop(), and which calls cause each transition?

How Events Reach actionPerformed

A Timer on its own is not useful. It does not draw, compute, or update any data itself. Its only job is to announce that a moment has arrived. Whatever should happen at that moment has to be written somewhere else, in an object that is set up to listen for those announcements. That object is supplied when the Timer is constructed, along with the delay between events.

The listener must be an object that implements the ActionListener interface. In practice this means its class is declared to implement ActionListener and defines the one method the interface requires, actionPerformed. ActionListener defines just that single method, so the class has exactly one job to satisfy the contract. Each time the Timer generates an event, actionPerformed runs.

java
Notes on the code
  • FrameListener declares that it implements ActionListener.
  • actionPerformed is the single method the interface requires; this is where per-frame work goes.
  • The Timer receives the delay and the listener when it is constructed.
  • start() must be called, or no events are ever generated.

The source describes the same division of labour in words: the Timer supplies the rhythm, and actionPerformed supplies the content of each frame. The sequence diagram below shows the order in which the three parts interact each time an event fires.

javax.swing.Timerfixed-delay beatActionListener objectsupplied at constructionactionPerformedcomputes and draws frame1. ActionEvent fires2. actionPerformed runs
In what order do the Timer, the listener object, and actionPerformed interact each time an event fires?

Why Stills Become Motion

Two conditions decide whether a viewer perceives continuous motion. The time between images must be short, and the change from one image to the next must not be too great. If both hold, the eye reads the sequence as one moving object. In a Timer-driven animation, the Timer's fixed delay sets the time between images, and the code in actionPerformed sets how much each new frame differs from the last.

Frame 1still imageFrame 2next positionFrame 3next positionnext ticknext tick
What happens on each tick, and how do successive frames combine into apparent motion?

Tracing One Animation Loop

Worked example

Moving a Shape One Step per Tick

A Timer fires every 50 milliseconds. Its listener moves a shape 10 pixels to the right each time actionPerformed runs. The shape starts at x = 0 and the Timer has just been started. What position does the shape show on the first five frames? (The numbers here are illustrative, chosen for this example.)

  1. 1
    Start

    start() is called, so the Timer moves from idle to running. No frame is drawn yet because no event has fired.

  2. 2
    Tick 1

    The Timer generates an event, the listener's actionPerformed runs, and the code computes x = 0 + 10 = 10 and draws the shape there.

  3. 3
    Tick 2

    The next event arrives after the same delay. actionPerformed computes x = 10 + 10 = 20 and draws the next still picture.

  4. 4
    Ticks 3 to 5

    The same pattern repeats. The positions drawn are 30, 40, and 50, one picture per event.

  5. 5
    Perception

    Five still pictures, each about 50 milliseconds apart and each 10 pixels from the last, are read by the eye as one shape sliding steadily to the right.

Result

The frames show x = 10, 20, 30, 40, 50 in turn, and the viewer perceives a continuous rightward motion.

Repeating Timer Versus Alarm Mode

The same Timer class can be configured to behave differently. It can be set to emit a single event after a specified delay, in which case it works like an alarm clock rather than a metronome. For animation, the repeating form is the one that matters, because a moving picture needs a new frame at every tick. The table below contrasts the two modes.

AspectRepeating TimerAlarm (single event)
Number of eventsOne every delay, until stoppedOne event after the delay
Typical useAnimation, a steady beat of framesA one-time action after a wait
Effect on actionPerformedRuns again and againRuns once
Stopping the event streamstop() turns the stream offEnds on its own after the single event

Two configurations of the same Timer class, as described in the source.

Mistakes Beginners Make with Timers

  • Common mistake

    Assuming the Timer starts when it is created

    What it looks like

    Timer t = new Timer(50, listener); with no further call, then waiting for the animation to begin.

    Why it fails

    A timer does not automatically start generating events when the timer object is created.

    Do this instead

    Call start() after construction to begin the event stream.

  • Common mistake

    Putting drawing or update logic in the wrong place

    Why it fails

    The Timer does not draw, compute, or update any data itself. Its only job is to announce that a moment has arrived.

    Do this instead

    Place the code that computes and draws each frame inside actionPerformed of the listener.

  • Common mistake

    Forgetting that the listener must implement the interface

    Why it fails

    The listener must be an object that implements ActionListener and defines actionPerformed.

    Do this instead

    Declare the class to implement ActionListener and define actionPerformed with its required signature.

  • Common mistake

    Expecting events to continue after stop()

    Why it fails

    stop() turns the stream of events off, and the listener is not invoked until start() is called again.

    Do this instead

    Call start() again when the animation should resume.

Finding Where an Animation Diverges

When an animation does not behave as expected, the source suggests splitting the system into three checkpoints and working through them in order. The first one that fails tells you where the divergence happens.

  1. 1
    Checkpoint 1: Was the Timer started? If start() was never called, or stop() was called and not followed by start(), no events will arrive.
  2. 2
    Checkpoint 2: Are events arriving at the listener? If the Timer is running but actionPerformed is not invoked, the listener was not supplied correctly at construction.
  3. 3
    Checkpoint 3: Does actionPerformed compute and draw the intended frame? If events arrive but the picture is wrong, the problem lies in the code inside the listener's method.
MEDIUM

An animation shows one still picture and never changes, even though the window opens correctly. Using the three checkpoints, name the first point that could fail and describe what you would check.

Hints
  • Start with checkpoint 1. Was start() called on the Timer?
  • If start() was called, check whether actionPerformed is being invoked at all.
  • Only if events arrive should you examine what the code inside actionPerformed draws.

Key Takeaways

  • A javax.swing.Timer generates ActionEvent events at a fixed delay, or it can be set to fire a single event as an alarm.
  • A listener implements ActionListener and defines actionPerformed, which runs each time the Timer generates an event; the listener is supplied when the Timer is constructed.
  • A Timer does not start on creation. start() begins the event stream, and stop() turns it off until start() is called again.
  • Animation works because a Timer-driven loop shows a sequence of still frames quickly enough, and with small enough changes, that the user perceives continuous motion.
  • When an animation misbehaves, check in order whether the Timer was started, whether events reach the listener, and whether actionPerformed computes and draws the intended frame.