Event Filters and Event Handlers are part of JavaFX’s comprehensive Event’s system. This covers mouse, keyboard, clipboard, dragging, and events and spans the entire scene graph from the window all the way to leaf nodes. All of that support allows you to execute code on every element of the Scene.

It also gives you complete control over where in an Event our action is executed. That comes down to the difference between Event Filters and Event Handlers.

What we’ll achieve in this tutorial

In this tutorial, we’ll go through:

  • The difference between Event Handlers and Event Filters – and why it’s important.
  • Real code examples of how to use Event Filters and attach them to nodes in your Scene.
  • A deeper look at when to use them, and their strategic benefit

In JavaFX, an Event Filter is an EventHandler object that is executed during the event capture stage of an Event action (rather than the bubbling phase). This allows you to exert control over the event behaviour of child nodes. If you didn’t understand some of the technical jargon there, don’t worry – click the drop down to look at Events in a little more detail.

1. Adding an Event Filter

To add an Event Filter to any node, you have to specify two things:

  • Event Type
  • Code to execute on the Event (this is an EventHandler object, even when registering an event filter!)

a. Event Type

As with all things Java, the Event Type is specified as an object. In this case, an EventType object.

There are several super types of events in JavaFX, dependant on the input source, and the type of action the source was executing at the time.

  • Input Event
  • Window Event
  • Action Event
  • Worker State Event

And quite a few more! Overall, there are 90 pre-defined EventTypes in JavaFX, which are hierarchically split into types by event source. For example, all user events like mouse clicks and key strokes are clustered into InputEvent and then sub-clustered into MouseEvent, KeyEvent and so on.

b. Code to execute

Defining the code to execute with an Event Filter is really simple. In spite of its name, an Event Filter is still an EventHandler object.

EventHander objects are a simple functional interface – that is, an interface Object with a single method. When we define the EventHandler, we overwrite the method, giving us an opportunity to write some code ourselves. Here’s an example with a KeyEvent that detects when a key has been pressed.

node.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<Event>() {
    @Override
    public void handle(Event event) {
        if(event.getCode() == KeyCode.ENTER){
            commitInputToDatabase();
        }
    }
});

As of Java 8, we can also define the functional interface using a lambda for more concise code:

node.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
    if(event.getCode() == KeyCode.ENTER){
        commitInputToDatabase();
    }
});

I prefer doing it this way because the additional hassle of defining the @Override annotation and including the method prototype in addition to the body don’t add anything to the logic you’re trying to put into.

2. When are Event Filters useful?

Event Filters are useful when a parent node explicitly needs to ensure an action happens regardless of the actions of its children. Or, when it wants to modify the behaviour of a child node during the Event Dispatch cascade.

a. Scene or window-level decision making

One of the most common use cases for an Event Filter is core window user expectations such as Open, Save, and Exit functions. They’re usually represented by commonly-understood key strokes like Ctrl+S or Command-S on Mac.

Applying an Event Filter to the Scene absolutely ensures that whatever functionality we want to happen is immediately completed. In this case, we’ll check for the key combination Ctrl+S.

KeyCombination save = new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN);
scene.addEventFilter(
         KeyEvent.KEY_PRESSED,
         event -> {
            if(save.match(event)) saveSession();
        }
);

If we want to, we can elect to end the Event Dispatch Chain immediately after our own action by calling event.consume(). This terminates the Event Dispatch Chain and prevents any extra nodes from firing their actions.

         event -> {
            if(save.match(event)) saveSession();
            event.consume();
        }

In short, consuming the event ensures no other nodes can add to or undo the functionality we’ve established.

Conclusions

Event Filters are triggered early in the JavaFX Event Dispatch Chain, meaning they are ideally suited to overriding child element behaviour.

We can add Event Filters easily to a target node by calling the addEventFilter() method, which takes two parameters:

  • Event Type
  • EventHandler object

The Event Type defines the circumstances under which the code is executed and the user or system action that produced it.