Part 13

Event handling

The user interfaces we've previously implemented have not been able to react to events in the user interface. This inability to react is not due to the components of the interface themselves, but due to the fact that we've yet to added any functionality that handles component events.

Button presses are handled using a class that implements the EventHandler interface. The type of the event in these cases is ActionEvent. The interface implementation specifies what is done when a user presses a button.

Button button = new Button("This is a button");
button.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        System.out.println("Pressed!");
    }
});

If desired, the explicit implementation of the interface can be replaced by a Lambda expression.

Button button = new Button("This is a button");
button.setOnAction((event) -> {
    System.out.println("Pressed!");
});

When the button is pressed, the program prints the text "Pressed!" to the console.

Event handlers attached to user interface components, such as the EventHandler used previously, are always connected to specific user interface components. Whenever an action is performed on a user interface component, e.g., a button is pressed, each of the event handlers attached to that particular component is called and the functionality written for them is executed.

We often want an event handler to change the state of some object. To get hold of an object, the event handler needs a reference to it. Let's think about the following user interface which has two text fields and a button.

@Override
public void start(Stage window) {
    TextField leftText = new TextField();
    TextField rightText = new TextField();
    Button button = new Button("Copy");

    HBox componentGroup = new HBox();
    componentGroup.setSpacing(20);
    componentGroup.getChildren().addAll(leftText, button, rightText);

    Scene viewport = new Scene(componentGroup);

    window.setScene(viewport);
    window.show();
}

There is a text field on both the left and right hand sides of the user interface. In addition to these, there's a button in the middle with the text "Copy".

Two text fields and a button with the text 'Copy'.

We'd like to have an application where the content of the left text field is copied over to become the content of the right text field when the user presses the button. This can be done with the help of an object implementing the EventHandler interface.

@Override
public void start(Stage window) {
    TextField leftText = new TextField();
    TextField rightText = new TextField();
    Button button = new Button("Copy");

    button.setOnAction((event) -> {
        rightText.setText(leftText.getText());
    });

    HBox componentGroup = new HBox();
    componentGroup.setSpacing(20);
    componentGroup.getChildren().addAll(leftText, button, rightText);

    Scene scene = new Scene(componentGroup);

    window.setScene(scene);
    window.show();
}

Now pressing the button results in the content of the left text field being copied to the text field on the right.

Two text fields and a button with the text 'Copy'.

NB! The method implemented can use objects that were declared before the method definition, as long as the values of the objects being used are not reassigned using the equals operator, i.e., the references do not change.

Loading

The eventhandler being used depends on what kind of user interface component we attach it to. If we want to listen to changes made to a text field character by character, then we would use the interface ChangeListener. In the example below we have attached an object implementing the ChangeListener interface to text field on the left. This object prints the changes in the text field to the console as well as sets the new value into the text field on the right.

leftText.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> change,
            String oldValue, String newValue) {

        System.out.println(oldValue + " -> " + newValue);
        oikeaTeksti.setText(newValue);
    }
});

In the previous example the changes being observed are in the text of the text field. Beacause text is in string format we have provided string as the type for the handler interface. As before, we can also express this code in a more compact form.

leftText.textProperty().addListener((change, oldValue, newValue) -> {
    System.out.println(oldValue + " -> " + newValue);
    rightText.setText(newValue);
});

The program can also do statistics. Calculating the values for the text fields in the previous exercise is quite straightforward. Following the example below, the values would be updated every time the user changes the content of the text field.

leftText.textProperty().addListener((change, oldValue, newValue) -> {
    int characters = newValue.length();
    String[] parts = newValue.split(" ");
    int words = parts.length;
    String longest = Arrays.stream(parts)
        .sorted((s1, s2) -> s2.length() - s1.length())
        .findFirst()
        .get();

    // set values of text elements
});
Loading
You have reached the end of this section! Continue to the next section:

Remember to check your points from the ball on the bottom-right corner of the material!