Events

Events are the bread-and-butter of GUI development. They fire whenever certain actions are performed by the user, or even by the application itself! Just like Exceptions (and most everything else), events are objects in Java, represented by javafx.event.Event or a subclass thereof:

Event Package Use
ActionEvent javafx.event The most common event class, use this if you're unsure.
InputEvent javafx.scene.input When the user inputs something, either from the mouse or keyboard
KeyEvent javafx.scene.input Subclass of InputEvent, used only for keyboard entries
MouseEvent javafx.scene.input Subclass of InputEvent, used only for mouse events (clicking, dragging, moving, etc.)
TouchEvent javafx.scene.input Used only on touch devices
WindowEvent javafx.stage Fired when the status of a Stage changes

In this section, we will refer to both events (the concept) and Events (the object). Event objects will used the monospaced code font, so pay attention!

Events (the concept) are made up of four key components:

  1. An Event object
  2. An event source, where it initially occurred
  3. An event target, where it is directed to. In most cases, the event source and target are the same
  4. An event handler, which is an object to listen for the events and deal with them as they occur. Event handlers implement the EventHandler interface, which requires them to define a public void handle<T event>() method. This is actually the most complicated part of events.

There are four main ways of using event handlers, in order from worst to best practices:

  1. Have the main class implement EventHandler
  2. Use an inner class
  3. Use an anonymous class
  4. Use a lambda expression

Let's examine each of these techniques in detail. Generally, using a lambda expression is the way to go, but it's useful to know all of these techniques, as sometimes you may need to use them!

Implementing EventHandler
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;

// EventHandler is a generic interface, so we must define
// what kinds of events we are listening for. Since we want
// to listen for button presses, we use ActionEvent.
public class AddSubtract extends Application
    implements EventHandler<ActionEvent>
{
    private static Button btnAdd;
    private static Button btnSubtract;
    private static Label lbl;
    private static int counter = 0;

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {
        // Create the add button
        btnAdd = new Button();
        btnAdd.setText("Add");
        // setOnAction() expects an object that implements EventHandler<T>
        // As it turns out, we do! This object's handle() method will be 
        // called
        btnAdd.setOnAction(this);

        // Create the subtract button
        btnSubtract = new Button();
        btnSubtract.setText("Subtract");
        btnSubtract.setOnAction(this);

        // Create the label
        lbl = new Label();
        // The autoboxed Integer class provides this toString(Integer) method
        lbl.setText(Integer.toString(counter));

        // Add the elements to an HBox pane. HBoxes organize elements 
        // horizontally. The constructor argument specifies the spacing 
        // between elements in pixels.
        //
        // HBox.getChildren() returns a list of the child elements. 
        // addAll() lets you add several elements at once.
        HBox pane = new HBox(10);
        pane.getChildren().addAll(lbl, btnAdd, btnSubtract);

        // Add the layout pane to the scene
        Scene scene = new Scene(pane, 200, 75);

        // Add the stage to the scene and show the window
        primaryStage.setScene(scene);
        primaryStage.setTitle("Add/Sub");
        primaryStage.show();
    }

    // We're overriding handle<T>() from EventHandler
    @Override
    // This is what is called when the event is fired
    public void handle(ActionEvent e)
    {
        if (e.getSource() == btnAdd) {
            counter++;
        } else if (e.getSource() == btnSubtract) {
            counter--;
        }
        lbl.setText(Integer.toString(counter));
    }
}

This is not the best practice. All of the logic is contained in one class, which is not very portable. What would be better would be to have a separate class to handle the event. This is a better object-oriented practice since it modularizes separate section of logic, increasing reusability. Let's see what that looks like:

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;

// No more interface!
public class AddSubtract extends Application
{
    private static Button btnAdd;
    private static Button btnSubtract;
    private static Label lbl;
    private static int counter = 0;

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {
        // Create a new instance of our event handler
        ClickHandler ch = new ClickHandler();

        // Create the add button
        btnAdd = new Button();
        btnAdd.setText("Add");
        // setOnAction() expects an object that implements EventHandler<T>
        // As it turns out, our event handler does!
        btnAdd.setOnAction(ch);

        // Create the subtract button
        btnSubtract = new Button();
        btnSubtract.setText("Subtract");
        btnSubtract.setOnAction(ch);

        // Create the label
        lbl = new Label();
        // The autoboxed Integer class provides this toString(Integer) method
        lbl.setText(Integer.toString(counter));

        // Add the elements to an HBox pane. HBoxes organize elements 
        // horizontally. The constructor argument specifies the spacing 
        // between elements in pixels.
        //
        // HBox.getChildren() returns a list of the child elements. 
        // addAll() lets you add several elements at once.
        HBox pane = new HBox(10);
        pane.getChildren().addAll(lbl, btnAdd, btnSubtract);

        // Add the layout pane to the scene
        Scene scene = new Scene(pane, 200, 75);

        // Add the stage to the scene and show the window
        primaryStage.setScene(scene);
        primaryStage.setTitle("Add/Sub");
        primaryStage.show();
    }

    // This is an example of an inner class. It abstracts out the 
    // event handling logic, allowing us to potentially use either 
    // class elsewhere.
    private class ClickHandler implements EventHandler<ActionEvent>
    {
        // We're overriding handle<T>() from EventHandler
        @Override
        // This is what is called when the event is fired
        public void handle(ActionEvent e)
        {
            if (e.getSource() == btnAdd) {
                counter++;
            } else if (e.getSource() == btnSubtract) {
                counter--;
            }
            lbl.setText(Integer.toString(counter));
        }
    }
}

This is a bit better. Now, we have clearly defined boundaries between our logic. Still, though, it's unlikely we'd ever use this ClickHandler elsewhere, since it's very specific in use. Therefore, we should instead use an anonymous inner class. An anonymous class is a class without a name (shocking!) that is defined where it is used. Pre-Java 8, anonymous classes were the best way to handle GUI events, since you can easily have a separate event handler for each event. Right now, our event handler has to check the source of the event, whether it is the add or subtract button. Let's see our code with anonymous classes:

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;

public class AddSubtract extends Application
{
    private static Button btnAdd;
    private static Button btnSubtract;
    private static Label lbl;
    private static int counter = 0;

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {
        // Create the add button
        btnAdd = new Button();
        btnAdd.setText("Add");
        // setOnAction() expects an object that implements EventHandler<T>
        // So, let's make one!
        btnAdd.setOnAction(
            new EventHandler<ActionEvent>()
            {
                public void handle(ActionEvent e)
                {
                    counter++;
                    lbl.setText(Integer.toString(counter));
                }
            }
        );

        // Create the subtract button
        btnSubtract = new Button();
        btnSubtract.setText("Subtract");
        btnSubtract.setOnAction(
            new EventHandler<ActionEvent>()
            {
                public void handle(ActionEvent e)
                {
                    counter--;
                    lbl.setText(Integer.toString(counter));
                }
            }
        );

        // Create the label
        lbl = new Label();
        // The autoboxed Integer class provides this toString(Integer) method
        lbl.setText(Integer.toString(counter));

        // Add the elements to an HBox pane. HBoxes organize elements 
        // horizontally. The constructor argument specifies the spacing 
        // between elements in pixels.
        //
        // HBox.getChildren() returns a list of the child elements. 
        // addAll() lets you add several elements at once.
        HBox pane = new HBox(10);
        pane.getChildren().addAll(lbl, btnAdd, btnSubtract);

        // Add the layout pane to the scene
        Scene scene = new Scene(pane, 200, 75);

        // Add the stage to the scene and show the window
        primaryStage.setScene(scene);
        primaryStage.setTitle("Add/Sub");
        primaryStage.show();
    }
}

Now we're really getting somewhere! We have unique event handlers for each event we want. Still, there has to be a better way. Luckily, there is!

Math Ahead

Java 8 introduced an idea from academic computer science circles (and practically every other useful programming language in existance) called lambda expressions. Lambda expressions were introduced originally in a mathematical technique called the λ calculus, which is nothing at all like differential/integral calculus. Without going into too much detail, the lambda calculus is a technique to express computing with functions (sound familiar?). So, say we have some function f(x, y), defined as follows:

f(x, y) = x*x + y*y

The lambda calculus introduced the idea of anonymous functions, like below:

(x, y) -> x*x + y*y
x -> (y -> x*x + y*y)

The finer aspects of the lambda calculus are irrelevant for the rest of this course, but if you are interested I would suggest looking into Scala, which is a functional programming language built on the JVM.

End math

Lambda expressions in Java are very restricted, in that they can only be used in place of anonymous classes that implement a single abstract method, and nothing more. Abstract methods are defined like this:

public abstract void someAbstractMethod();

Abstract methods are defined in a superclass or interface, and implemented in a subclass or class that implements that interface. Classes that have one and only one abstract method, and nothing else, are called functional interfaces. As it turns out, EventHandler is just such an interface! Therefore, we can turn our anonymous class code:

new EventHandler<ActionEvent>()
{
    public void handle(ActionEvent e)
    {
        counter++;
        lbl.setText(Integer.toString(counter));
    }
}

Into code that uses a lambda expression for the abstract method:

e -> {
    counter++;
    lbl.setText(Integer.toString(counter));
}

The basic format of a Java lambda expression is like this:

<method args> -> { 
    <some code> 
}

If the code to run is only one line, we can simply write:

<method args> -> <some code>

How do you know, however, what is a functional interface? Read the documentation!! Anytime you see a superclass or interface that has one and only one abstract method, and nothing else, you can substitute a lambda expression for that functional interface.

Here's our final version of the application using lambdas:

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;

public class AddSubtract extends Application
{
    private static Button btnAdd;
    private static Button btnSubtract;
    private static Label lbl;
    private static int counter = 0;

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {
        // Create the add button
        btnAdd = new Button();
        btnAdd.setText("Add");
        // setOnAction() expects an object that implements EventHandler<T>
        // So, let's use a lambda instead!
        btnAdd.setOnAction(
            e -> {
                counter++;
                lbl.setText(Integer.toString(counter));
            }
        );

        // Create the subtract button
        btnSubtract = new Button();
        btnSubtract.setText("Subtract");
        btnSubtract.setOnAction(
            e -> {
                counter--;
                lbl.setText(Integer.toString(counter));
            }
        );

        // Create the label
        lbl = new Label();
        // The autoboxed Integer class provides this toString(Integer) method
        lbl.setText(Integer.toString(counter));

        // Add the elements to an HBox pane. HBoxes organize elements 
        // horizontally. The constructor argument specifies the spacing 
        // between elements in pixels.
        //
        // HBox.getChildren() returns a list of the child elements. 
        // addAll() lets you add several elements at once.
        HBox pane = new HBox(10);
        pane.getChildren().addAll(lbl, btnAdd, btnSubtract);

        // Add the layout pane to the scene
        Scene scene = new Scene(pane, 200, 75);

        // Add the stage to the scene and show the window
        primaryStage.setScene(scene);
        primaryStage.setTitle("Add/Sub");
        primaryStage.show();
    }
}

























What if we have a lot of code to run when we have an event? Our lambda expression would get very unwieldy! Well, since the body of a lambda expression is just Java code, we can abstract it out into a separate method called by our lambda expression.

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.event.*;

public class AddSubtract extends Application
{
    private static Button btnAdd;
    private static Button btnSubtract;
    private static Label lbl;
    private static int counter = 0;

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {
        // Create the add button
        btnAdd = new Button();
        btnAdd.setText("Add");
        // setOnAction() expects an object that implements EventHandler<T>
        // So, let's use a lambda!
        btnAdd.setOnAction(e -> btnAddClick());

        // Create the subtract button
        btnSubtract = new Button();
        btnSubtract.setText("Subtract");
        btnSubtract.setOnAction(e -> btnSubtractClick());

        // Create the label
        lbl = new Label();
        // The autoboxed Integer class provides this toString(Integer) method
        lbl.setText(Integer.toString(counter));

        // Add the elements to an HBox pane. HBoxes organize elements 
        // horizontally. The constructor argument specifies the spacing 
        // between elements in pixels.
        //
        // HBox.getChildren() returns a list of the child elements. 
        // addAll() lets you add several elements at once.
        HBox pane = new HBox(10);
        pane.getChildren().addAll(lbl, btnAdd, btnSubtract);

        // Add the layout pane to the scene
        Scene scene = new Scene(pane, 200, 75);

        // Add the stage to the scene and show the window
        primaryStage.setScene(scene);
        primaryStage.setTitle("Add/Sub");
        primaryStage.show();
    }

    private void btnAddClick()
    {
        counter++;
        lbl.setText(Integer.toString(counter));
    }

    private void btnSubtractClick()
    {
        counter--;
        lbl.setText(Integer.toString(counter));
    }
}