JavaFX’s user interfaces are almost completely event-driven. Most people are familiar with action events, key events and mouse events, but almost every complex update to a user interface is accomplished using events of one sort or another. And yet, Events are one of the most under-played parts of JavaFX.

JavaFX Events can account for almost all executable code required to update a user interface
Events can be used to drive almost all interface updates

As a general rule, if you need to accomplish a simple value-based change, this can be done with Property binding. For everything else, I use events.

What is an Event?

If you’ve ever sorted a column in a TableView, changed a TreeView, or edited a cell, you’ve already used events to update the interface in the background. Events are a versatile, stable way to drive changes on the JavaFX Application thread by setting executable code ahead of time.

An Event is any change of state in an input device (such as mouse or touch screen), a user action (ActionEvent), or a background task. Events can also be fired as a result of scroll or edit events on complex nodes such as TableView and ListView.

At a very basic level, the Event object is a class with a surprisingly small number of parameters. It doesn’t hold any executable code, nor does it run any code. It’s a flag to the system that something has changed.

JavaFX then provides significant support to run executable code, defined separately through the EventHandler class, on any changes of state. There are 90 separate types of event supported by JavaFX, with the capability to extend the Event class and define additional custom functionality.

How to use this resource

Events are such a fundamental part of JavaFX, I thought they deserved an attempt to summarise everything in one place. I’ll attempt to do that justice by going through:

  • How Events work in JavaFX and how it’s handled when fired
  • How to define executable code in an EventHandler and use them effectively to control the behaviour of your application
  • Event types. All of them. From mouse clicks to background tasks

I’ve tried to make this article as readable as possible, whilst also including as much of the useful information that usually gets left out. To that end, low-level detail and code examples are inside drop-downs to make scrolling a little less painful.

1. How Events Work

I wasn’t lying when I said an Event is surprisingly simple. What is complicated is the background processes that implement the event handling system. In this section, we’ll go through what defines an Event, and how to set up your code to execute whenever a certain event is detected.

The Event Class

In JavaFX, an Event has four properties:

  • Source
  • Target
  • Type
  • Consumed

The boolean parameter consumed is usually missed, but it’s really important to application behaviour. Let’s go through each one in turn.

a. Source

JavaFX assigns a node as the source of an event at creation, which is defined according to the cause of the event (mouse, key, action etc).

b. Target

JavaFX assigns a node as the target of an event at creation. This is the node that has been acted upon by the event. All events throughout the lifecycle of the Event are delivered to the same Target node.

c. Type

JavaFX assigns the event type based on the origin of the event (mouse, key, action etc.) and the nature of the state change (key released, mouse clicked, task started and so on).

d. consumed

At any point in this process, including before an event has reached its target, the event can be consumed and the process immediately stops.

I can’t understate how useful this is in controlling events throughout a scene and how often you’ll use it in event-driven development.

Event Firing – Phases

Whenever an event fires in JavaFX, it travels from the scene to the node and then from the node back up to the scene. This requires three things:

  1. Knowing which node the event should travel to
  2. Identifying which Window, in multi-window applications, and
  3. Knowing the route to take between the scene and the node

These are the first steps in firing an event. Step 1 is called Target Selection. Steps 2 and 3 are taken together in a process called Route Construction.

We’ll go through each of these and then finish by going through how events are fired.

a. Target Selection

JavaFX selects the target of an event based on the type of event that has occurred and the input device that has created the event.

Because target selection is type-dependent, I’ve taken a more detailed look at target selection for each event type in the drop downs below.

b. Route Construction

In the case of foreground events – events where the target is a node within the scene – the target node creates a route between itself and the Window.

The logic goes something like this:

Get all the nodes between here and the Scene:

  1. Add target node to the route as the end (tail).
  2. If I target has a parent, append the parent node to the route
  3. Continue adding parents until I find a node that doesn’t have a parent
  4. This is the root node.
  5. Use the root node to get the Scene.

Then, get the extra dispatch chain for the scene and window:

  1. For the Scene just identified, add itself to the event dispatch chain (route)
  2. Use the Scene to get the Window
  3. Add the wind;es EventDispatcher as the very last link in the event dispatch chain.

After this, we return the route created from the initial call of buildEventDispatchChain() on the Target node.

For background events, the dispatch chain building process is much shorter. The EventHelper class contains a dispatch chain intended for Tasks and Services. The event is just added to the Helper event dispatcher and returned.

After route construction, events are ready to be dispatched from every link in the dispatch chain.

c. Event Capturing

Now we have a dispatch chain, the event starts at the first element of the chain and is forwarded, one-by-one, through every node’s EventDispatcher until the target node is reached.

This is where the difference between event filters and event handlers is important. If you haven’t heard of event filters, they can be added to any EventTarget (nodes, scene, charts, tasks and so on) using the addEventFilter() method.

Event filters are great for controlling the behaviour of child nodes, because they are executed by parents before children as the event traverses the scene graph.

Note: A node needs to have an event filter of the correct type for that filter to fire.

Event filters fire in the event capturing phase of the event handling process

Event filters can be added to any EventTarget (nodes, tasks and services) by invoking addEventFilter() with the right EventType and EventHandler.

(check out How to define executable code in an EventHandler below)

Once the EventDispatcher on the target node has fired any relevant filters, it fires any relevant handlers and passes the event back up the chain in a process known as event bubbling.

d. Event Bubbling

Events registered with targets are executed in ascending order from the event target, to the Window.

Note: A node needs to have an event handler of the correct type for that handler to fire.

Event handlers can be added in the same way as event filters, but by using invoking addEventHandler() method on the target.

Let’s see how to define event filters and event handlers.

2. How to define executable code in an EventHandler

Whenever you want to define a response to an event, we need to use an EventHandler object. In spite of its name, we use the same object for both event filters and event handlers.

Creating an EventHandler:

We can do this by defining the EventHandler as an anonymous inner class, or as a lambda expression.

Defining an EventHandler

The EventHandler object is an object with a single method, which we need to override when we instantiate it. As the EventHandler interface is parameterized, we need to specify what event type we’re handling. In this case we’ll use a MouseEvent.

EventHandler<MouseEvent> eventHandler = new EventHandler<>() {
    @Override
    public void handle(MouseEvent event) {
        //executable code
    }
};

Replacing the definition with a lambda

An EventHandler is a functional interface (an interface with a single method). For convenience, the handle() method of the EventHandler interface can be specified with a lambda instead.

EventHandler<MouseEvent> eventHandler = event -> {
    //executable code
};

Adding and removing event filters and handlers

When we define and add an event handler to a node (or task), we define what type of that event we want our code to trigger from. As an example, if we want our code to trigger from any mouse event on the root node of our scene, we can use the event type MouseEvent.ANY.

If you want to remove an event handler at a later point in code, you will need to keep a reference to the EventHandler object you create.

//define the handler
EventHandler<MouseEvent> eventHandler = event -> {
    //executable code
};
//add the handler
rootNode.addEventHandler(
        MouseEvent.ANY, //specify the type of event to listen for
        eventHandler);
//later remove the handler
rootNode.removeEventHandler(
        MouseEvent.ANY, //this should be the same as defined above
        eventHandler
);

It’s equally easy to define and remove event filters using the addEventFilter() and removeEventFilter() methods.

A few simple things to remember though:

  • If you added an event handler, you’ll need to remove an event handler (not a filter) and vice versa.
  • The event type should be the same when you remove it as when you add it.

Using convenience methods

Sometimes it’s expedient to use convenience methods like setOnMousePressed() to define event handlers. The syntax for event handlers is:

setOn EventType ()

Here are a few examples:

  • setOnMousePressed() (nodes)
  • setOnKeyReleased() (nodes)
  • setOnTaskRunning() (tasks)
  • setOnScrollFinished() (nodes with virtual flow)

Convenience methods are useful because you can remove the handler without having to remember the reference by invoking the same method and passing a null reference:

setOnMousePressed(null)

They are also guaranteed to execute after any other event handlers of the same type on that node.

3. Event Types

In general, events can be categories into input events, and events specific to JavaFX. ActionEvents require input, but also span input types, and are created internally by JavaFX, so I’ve included these in JavaFX-specific events, rather than input ones.

Operating System-Generated (Input) Events

Input events, such as mouse, keyboard and gesture events, happen above the level of the application, and are passed to our app by the operating system. The JavaFX background processes use this information to identify the right node to enact the event on.

JavaFX input events are provided by the operating system and populated by JavaFX background procedures

This integration with the operating system is actually really handy. If the user tries to click, and wobbles the mouse a little bit – but inside a system-defined safe-area called the “hysteresis position” – the event doesn’t count as a drag.

For each event, I’ve set out the operating-system parameters your don’t need to worry about (or can’t control) as well the types of event in each case, how to use them, and any quirks of behaviour it’s useful to be aware of.

a. Key Events

Key events are generated by the operating system whenever a key changes state – that is, it’s pressed or released. If a key remains pressed for a long time, the OS will initiate additional key events.

a. Mouse Events

The MouseEvent class defines any change in the mouse cursor position or button states. As with key events, they are provided by the operating systemto the application, alongside the default mouse behaviour expectations that have been set by the user.

a. Touch Events

JavaFX provides support for touch-sensitive screens on laptops, tablets and mobile devices, and can also generate mouse events in JavaFX.

a. Gesture Events

Gestures are part of JavaFX’s in-built support for touch-sensitive devices. There are four types of gesture: rotate, swipe, scroll and zoom.

JavaFX-originated Events

JavaFX also has the ability to create and dispatch its own events. There are three types of these events:

  • Action Events
  • Worker Events
  • Edit, Modification and Sorting Events

Each of these gives JavaFX the ability to layer functionality over the user interface.

a. Action Events

Action events are designed to span multiple input types, defining a single piece of executable code to use int he case of a specific user action. Controls such as the TextField and buttons have action events.

b. Worker State Events

Long-running tasks can easily be outsourced to workers such as Tasks or Services. It’s can be useful to fire events as these progress.

c. Edit, Modification and Sorting Events

These events span a number of uses, but they’re all responsible for updating complex UI elements that can’t be achieved through property binding.

Conclusions

Events are one of the most important parts of JavaFX. Almost all of executable code you need to update a user interface can be accomplished with events and property binding.

Events mediate the vast majority of interactions with the user. For anything more complex than binding the text content of a TextField to a FilteredList, events should be used to drive UI updates based on user interaction.

Even when a user’s typing, I use events preferentially over change listeners, because events are more stable, and you’re less likely to get multiple change events triggering for a single key stroke.

Finally, events are also good for mediating between complex background tasks and the user interface. The best way to get around a hanging UI is to use tasks, and to attach an event to the completion of that task.