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:
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.
The difference between an Event Filter and an Event Handler comes down to timing.
Whenever you trigger an event in JavaFX, a JavaFX background thread takes 4 steps to resolving the event’s effect:
For the purposes of this tutorial, you only need to know the difference between capture and bubbling. So, we’ll take a rocket-fuelled look at target selection and route construction before cutting to the chase.
That way, we can see what impact JavaFX’s timing rules have for your program.
The key difference between Event Filters and Event Handlers is that Event Filters are fired during the ‘capture’ phase. Event Handlers are fired during the ‘bubbling’ phase.
Apart from making JavaFX’s Event support sound like a 4 year old’s birthday party, these terms can be difficult to understand without context. This article will take you through each section before giving some real examples of how to add them in your code.
1. Target Selection & Route Construction
As soon as an Event is triggered, JavaFX begins to work out in the background what triggered the Event. It has systematic rules to determine the source based on the type of event that’s been triggered.
Once JavaFX knows the Event Target, it calculates the route between the Scene (the highest element in the scene graph) and the Target. Because every node can only have one parent, there is only one route between the scene and the target.
This phase is absolutely critical, because in the capture phase, JavaFX is going to go down the route, firing every Event Filter it finds on the way to the target. Then, in the bubbling phase, JavaFX ‘bubbles’ back up the route firing every Event Handler it finds on the way back up.
It it very important to understand that every Event Filter and Event Handler has the opportunity to consume an Event that is passed to it. If this happens, the Event Dispatch Chain terminates immediately. No further Filters or Events are fired. This defines the power of the Event Filter: it gives a Stage, Scene or Parent overriding control of the Event Chain with respect to nodes below it in the scene.
You can consume an event in any handler or filter by calling event.consume()
, where ‘event’ is the Event
passed to the Handler or Filter.
2. Event Capture
JavaFX kicks off the Event Capture phase at the Stage level, moving down the chain searching for Event Filters. It follows simple rules to determine what to do:
Once the last Event Filter has been fired on the target element at the bottom of the Event Dispatch Chain, the Capture phase ends, and bubbling begins.
3. Event Bubbling
The bubbling phase begins at the Target element (the bottom), and rises to the top of the scene (like a bubble).
Again for every node, it fires every Event Filter it finds – in decreasing order of specificity – and stops when event.consume()
is called.
1. Adding an Event Filter
To add an Event Filter to any node, you have to specify two things:
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:
The Event Type defines the circumstances under which the code is executed and the user or system action that produced it.