taylorialcom/ GUI

Event Handling in JavaFX

Writing GUI applications requires that program control be driven by the user's interaction with the GUI. When the user moves the mouse, clicks on a button, or selects an item from a menu an "event" occurs.

The javafx.event package provides the basic framework for FX events. The Event class serves as the base class for JavaFX events. Associated with each event is an event source, an event target, and an event type.

The operating system or windowing system captures generated events and notifies the listener objects when a type of event they are listening for has occurred. The operating system notifies the listener object by sending a message to (calling) the event handling method. If no listener object is subscribed to listen for the event that occurred, the event is ignored.

Types of Events

There are many subclasses of the Event class:

EventHandler I — Separate Class

The EventHandler interface is a functional interface with one method: void handle(T event). Here we will focus on enabling functionality when a button is pressed. In order to do this, we need to:

Sample Code

Here is an example of a simple JavaFX application that has one button:

public class SimpleGUI extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        Pane root = new StackPane();
        Button clickMe = new Button("Click Me");
        clickMe.setOnAction(new ClickMeHandler());
        root.getChildren().add(clickMe);

        primaryStage.setTitle("Simple GUI");
        primaryStage.setScene(new Scene(root, 400, 300));
        primaryStage.show();
    }
}

Here we:

The ClickMeHandler class implements the EventHandler class:

public class ClickMeHandler implements EventHandler<ActionEvent> {
    @Override
    public void handle(ActionEvent event) {
        Button button = (Button)event.getSource();
        if(button.getEffect()==null) {
            button.setEffect(new BoxBlur());
        } else {
            button.setEffect(null);
        }
    }
}

Here we:

EventHandler II — Inner Class

Now let's add a label to the GUI. Instead of blurring the button, let's blur the label when the button is clicked. Now we have a problem. Can you identify the problem?2

The following code solves that problem.

public class SimpleGUI extends Application {

    private Label label;

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

    @Override
    public void start(Stage primaryStage) {
        Pane root = new StackPane();
        label = new Label("Here is some text that can be manipulated with the button above.");
        Button clickMe = new Button("Click Me");
        clickMe.setOnAction(new ClickMeHandler());
        root.getChildren().addAll(label, clickMe);

        primaryStage.setTitle("Simple GUI");
        primaryStage.setScene(new Scene(root, 400, 300));
        primaryStage.show();
    }

    private class ClickMeHandler implements EventHandler<ActionEvent> {
        @Override
        public void handle(ActionEvent event) {
            if(label.getEffect()==null) {
                label.setEffect(new BoxBlur());
            } else {
                label.setEffect(null);
            }
        }
    }
}

This may seem strange, but we are actually declaring a class within another class. Here ClickMeHandler is known as an inner class.

EventHandler III — Anonymous Inner Class

If we had more buttons, each button could have a corresponding inner class that implements an event handler for the button. In all of these situations, it likely that we will only ever create one object from each of these inner classes. If we are only going to create one object from the class, we can make use of an anonymous inner class. Here's how:

public class SimpleGUI extends Application {

    private Label label;

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

    @Override
    public void start(Stage primaryStage) {
        Pane root = new StackPane();
        label = new Label("Here is some text that can be manipulated with the button above.");
        Button clickMe = new Button("Click Me");
        clickMe.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                if(label.getEffect()==null) {
                    label.setEffect(new BoxBlur());
                } else {
                    label.setEffect(null);
                }
            }
        });
        root.getChildren().addAll(label, clickMe);

        primaryStage.setTitle("Simple GUI");
        primaryStage.setScene(new Scene(root, 400, 300));
        primaryStage.show();
    }
}

EventHandler IV — Lambda Expression

Hopefully the above discussion has helped you get a better understanding of the different approaches to handling events. The following sample is similar to the one in the previous section, but here we replace the anonymous inner class with a lambda expression.

public class SimpleGUI extends Application {

    private Label label;

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

    @Override
    public void start(Stage primaryStage) {
        Pane root = new StackPane();
        label = new Label("Here is some text that can be manipulated with the button above.");
        Button clickMe = new Button("Click Me");
        clickMe.setOnAction(event -> {
            if(label.getEffect()==null) {
                label.setEffect(new BoxBlur());
            } else {
                label.setEffect(null);
            }
        });
        root.getChildren().addAll(label, clickMe);

        primaryStage.setTitle("Simple GUI");
        primaryStage.setScene(new Scene(root, 400, 300));
        primaryStage.show();
    }
}

Grand Finale

In order to separate the code associated with handling the event, I recommend extracting it into a helper method. The above class can be rewritten as:

public class SimpleGUI extends Application {

    private Label label;

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

    @Override
    public void start(Stage primaryStage) {
        Pane root = new StackPane();
        label = new Label("Here is some text that can be manipulated with the button above.");
        Button clickMe = new Button("Click Me");
        clickMe.setOnAction(event -> handleClickMe(event));
        root.getChildren().addAll(label, clickMe);

        primaryStage.setTitle("Simple GUI");
        primaryStage.setScene(new Scene(root, 400, 300));
        primaryStage.show();
    }

    private void handleClickMe(ActionEvent event) {
        if(label.getEffect()==null) {
            label.setEffect(new BoxBlur());
        } else {
            label.setEffect(null);
        }
    }

}

If the lambda expression is just a call to another method, we can replace the lambda expression with a method reference. Here we would replace:

        clickMe.setOnAction(event -> handleClickMe(event));

with:

        clickMe.setOnAction(this::handleClickMe);

which makes the handleClickMe(ActionEvent event) method get called whenever the button generates an ActionEvent.

1

The InvalidationListener is used for observable controls like sliders, menus, etc.. The ChangeListener interface may be used instead of the InvalidationListener if, in the listener, you need to know the new value of the item being observered.

2

One of the problems with the approach taken in EventHandling I is that the event handler object does not have access to any GUI elements except the button (since we can get it by calling getSource()).