Concepts / Java Events and Event Listeners

Java Events and Event Listeners

A custom component is written by subclassing an existing Java component class, which lets you keep inherited behavior and change only what your design needs.

  • Programming

Why Standard Components Fall Short

Most user interfaces in Java can be assembled from the components the language already supplies, such as buttons, labels, text fields, and panels. You place them, arrange them, and wire up their events, and the job is done. Eventually, though, a design asks for something no ready-made class offers. The interface might need a dial that responds to dragging, a graph that redraws itself, or a display that reacts to a click in a precise way. When that happens, the question changes from which component to use to how you can make a component that does what you need.

This article follows one concrete case from start to finish: a stopwatch label that starts timing when it is clicked and stops timing on the next click. The stopwatch is a good teaching case because it is simple enough to trace by hand, yet it forces you to answer the two questions every custom component raises: what must the component remember between events, and how should it respond to each event?

Subclassing JPanel as the Starting Point

Definition

A custom component is written by subclassing an existing Java component class. You write your own class that extends one of Java's component classes. You do not start from nothing: you inherit everything the parent class already does, and you add or change only the behavior your design needs.

Choosing the parent is the first decision. Sometimes the parent is a plain, blank surface, and sometimes it is a more specialized component that already handles much of the work. A panel of type JPanel is the most common starting point for a custom drawing surface. It begins with no content of its own, which makes it a clean canvas. Once you subclass JPanel, you decide what appears on the canvas and how the component reacts to mouse and keyboard input. Subclassing gives you that control without forcing you to rebuild the basic machinery of a component.

Inheritance works in layers. The standard class supplies the base behavior. Your subclass sits beneath it and inherits that behavior. The specific component you build, such as the stopwatch, is a further refinement. Each level only has to deal with the differences from the level above it. This is why a custom stopwatch does not need to re-implement how a panel is laid out or how it is placed in a window; it only adds the timing behavior and the response to clicks that the design requires.

Best practice

Subclass the most specialized parent that already fits your need. If a blank canvas is enough, JPanel is a clean base. Only reach for the broader choice when the parent's existing behavior actually conflicts with the design, because every inherited behavior you keep is work you do not have to rebuild or test.

Tracing the Stopwatch's Memory

Before writing any event code, consider what the component must remember between events. A stopwatch needs two pieces of memory. The first is the moment timing began, which must survive from one click to the next. The second is whether the stopwatch is currently running, because the same click means start on one occasion and stop on another. Both pieces belong in instance variables, so that every event handler sees the same values. In the source's terms, a component that must remember information between events stores that information in instance variables, such as a start time and a running flag.

Stoppedrunning = falseRunningrunning = trueclick: store start time, set running trueclick: stop timing, set running false
What values does the stopwatch hold before, during, and after each mouse press, and which press changes which state?
Worked example

Tracing Three Clicks on a Fresh Stopwatch

A stopwatch component starts with running set to false and no start time recorded. The user clicks it three times in a row. What does each click do to the stored state?

  1. 1
    Click 1, before state

    The stopwatch is not running, so the click means start. The handler records the start moment and sets running to true.

  2. 2
    Click 1, after state

    The start time now holds the moment of the first click and running is true.

  3. 3
    Click 2, before state

    The stopwatch is running, so the same kind of click now means stop. The start time is kept, because it is what the stop needs to measure from.

  4. 4
    Click 2, after state

    Timing has stopped and running is false.

  5. 5
    Click 3, before state

    The stopwatch is not running again, so this click means start, exactly as the first click did.

  6. 6
    Click 3, after state

    A new start moment is recorded and running is true once more.

Result

The same mouse press produces different results depending on the running flag. The flag is what lets one handler decide between start and stop.

Clickrunning beforeAction takenrunning after
1falseStart: record start momenttrue
2trueStop: end timingfalse
3falseStart: record new start momenttrue

The running flag decides whether a press starts or stops the stopwatch.

Choosing a Time Source for a Click

Once the stopwatch knows it must record a start moment, the next question is where that moment comes from. Java expresses time as a count of milliseconds. The call System.currentTimeMillis() gives you the current time under that measure. Because the count grows large quickly when measured in milliseconds, the variable that holds it should be of type long, which can store much larger values than int.

Following One Click Through Java

The sequence below shows what happens after the user clicks the stopwatch. The click is delivered to the event machinery, which calls the handler and hands it a MouseEvent. The handler reads the event's time and uses it to update the stopwatch's instance variables. Each step depends on the one before it, so the order matters.

Userpresses the mouseJava event systemdelivers the clickMouse handlerreceives MouseEventStopwatch fieldsstart time and running flag1. physical click2. calls handler with event3. reads getWhen and updates state
What happens in order after the user clicks: which object receives the MouseEvent, which method runs, and what gets updated?
  1. 1
    The user physically presses the mouse on the stopwatch component.
  2. 2
    Java delivers the click as a MouseEvent and calls the mouse-pressed handler.
  3. 3
    The handler asks the event for its time with getWhen(), which reports when the click occurred.
  4. 4
    The handler checks the running flag to decide whether this press starts or stops timing.
  5. 5
    The handler updates the instance variables: it stores a new start time when starting, or it ends timing and sets the flag to false when stopping.

Mistakes Developers Make With Timing

  • Common mistake

    Reading the clock inside the handler for a click timestamp

    Why it fails

    currentTimeMillis() reports when the handler runs, not when the user clicked. Some delay can occur between the physical click and the handler call.

    Do this instead

    Read the time from the event with getWhen() when the timing must reflect the click itself.

  • Common mistake

    Storing the start time or running flag in local variables

    Why it fails

    A local variable disappears when the handler returns, so the next click would have no record of when timing began or whether the stopwatch is running.

    Do this instead

    Keep the start time and running flag as instance variables so every event handler sees the same values.

  • Common mistake

    Storing milliseconds in an int

    Why it fails

    Millisecond counts grow large quickly, and an int may not hold the values a clock produces.

    Do this instead

    Store the time in a long variable, which can hold much larger values.

  • Common mistake

    Treating every click the same way

    Why it fails

    The same mouse press means start when the stopwatch is stopped and stop when it is running. Ignoring the running flag makes the behavior wrong on alternate clicks.

    Do this instead

    Check the running flag in the handler before deciding which action the press performs.

  • Common mistake

    Rebuilding basic component behavior from scratch

    Why it fails

    Starting from nothing forces you to re-implement behavior the parent class already provides.

    Do this instead

    Extend an existing component class such as JPanel and change only the behavior your design needs.

Predicting the Stopwatch After Three Clicks

What do you think happens?

A fresh stopwatch receives three clicks in a row. After the third click, is it running?

  • Yes, it is running

  • No, it is stopped

Reveal answer
Answer:

Yes, it is running.

The first click starts timing and sets running to true. The second click stops timing and sets running to false. The third click starts timing again, so running is true afterward. The same kind of press alternates between start and stop because the running flag decides which action happens.

MEDIUM

Using the state table from this article, describe what the stopwatch's start time should hold after the second click, and explain why it should not be overwritten at that point. Then describe what would change if the timing were read with currentTimeMillis() inside the handler instead of getWhen().

Hints
  • Think about what the stop action needs in order to measure elapsed time.
  • Compare when the clock is read in each approach relative to the physical click.

Key Takeaways

  • A custom component is built by subclassing an existing Java component class such as JPanel, inheriting its behavior and changing only what the design needs.
  • A stateful component keeps what it must remember, such as a start time and a running flag, in instance variables so every event handler sees the same values.
  • The same mouse press can mean start or stop, so the handler must check the stored state to decide which action to perform.
  • MouseEvent.getWhen() reports when the click occurred and is the better choice for timing a user action, while System.currentTimeMillis() reports the time at the moment it is called, which may be later than the click.
  • Store millisecond times in a long variable, since the counts grow large quickly.