A program that waits, instead of a program that runs top to bottom
Every program you have written so far ran your steps, in your order, start to finish. A GUI program runs a fixed setup, then waits — and your code only runs again when the user does something.
After this lesson
You should be able to
- Explain what an event source and an event listener are, in relation to each other.
- Register a listener on a component and explain when Java calls it.
- Contrast this event-driven flow with the top-to-bottom flow of every CS105ES program.
Inverting control: the program waits for you
Every C program from CS105ES followed main from the first line to the last, in an order the programmer fully controlled. A GUI program flips that: main sets up the window and its buttons, then hands control to Java's event loop, which sits idle until the user clicks, types, or moves the mouse. Your code runs in short bursts, triggered by the user, not in one continuous top-to-bottom pass.
This inversion is called event-driven programming, and it is the reason GUI code looks structurally different from anything written so far in this course — not because the language changed, but because the entire shape of when your code runs has changed.
Source, event object, and listener
An event source is a component that can generate events — a JButton, for instance. When the user clicks it, Java constructs an event object carrying details about what happened, and looks for a listener registered on that source. A listener is an object implementing a specific listener interface, with a method Java calls automatically the moment the matching event occurs.
This is why registering a listener with addActionListener is called delegation: the button itself contains no application logic at all, it simply delegates "something happened here" to whichever listener object you registered, keeping the component reusable and your logic separate from it.
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JButton;
JButton greetButton = new JButton("Greet");
greetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Button was clicked!");
}
});Try it yourself
Create two buttons, "Increment" and "Reset". Register a listener on each that updates and prints a counter variable — Increment adds one, Reset sets it back to zero. Note in one sentence why the counter variable cannot be a plain local variable inside main.
Need a hint?
A listener's method runs long after main has already finished setting up the window — any variable it needs must still be reachable at that later moment.
Check the worked solution
The counter must be a field of an enclosing class (or effectively final in this simplified form), not a plain local variable, because the listener's actionPerformed runs much later than main's own code — long after any ordinary local variable inside main would have already gone out of scope, exactly the scope-and-lifetime reasoning from CS105ES applied to a genuinely new timing situation.
class CounterDemo {
private int count = 0; // a field, still reachable when listeners fire later
void setup() {
JButton incrementButton = new JButton("Increment");
JButton resetButton = new JButton("Reset");
incrementButton.addActionListener(e -> {
count++;
System.out.println(count);
});
resetButton.addActionListener(e -> {
count = 0;
System.out.println(count);
});
}
}Quick check
Why does registering greetButton.addActionListener(...) not immediately run the code inside actionPerformed?
Why this lesson exists
Syllabus mapping
Event Handling: Events, Event sources, Event classes, Event Listeners, Delegation event model.
Maps to course outcome CO4.