I am trying to understand when to use EventDispatcher in JavaFX. I am pretty much aware of all the event mechanisms of capturing and bubbling. But I am still confused with t
EventDispatcherAs the name suggests, the purpose of an EventDispatcher is to dispatch an
Event. The way it does this is dependent on the implementation. However, the
standard implementation (internal to JavaFX) makes EventDispatcher a kind of
"collection" of EventHandlers. It is the responsibility of the EventDispatcher
to invoke the appropraite EventHandler at the correct time. What makes
an EventHandler "appropriate" depends on:
EventHandler was registered for the current phase of the event
dispatching cycle (capturing or bubbling)EventHandler was registered for the current Event's EventType or
one of the supertypes (i.e. EventType.getSuperType())If we focus on the scene-graph, when an Event is fired it starts at the top
of the scene-graph (the Window) and travels through the hierarchy to
the target (usually a Node, but at the very least an implementation of
EventTarget). This is the "capturing" phase of the event dispatch cycle. After
reaching the target it travels back up the scene-graph until it reaches the
Window again. This is the "bubbling" phase of the cycle.
Window -> Scene -> Root Node -> Middle Node -> EventTargetWindow <- Scene <- Root Node <- Middle Node <- EventTargetIf at any step the Event is consumed (via Event.consume()) then it will not
be forwarded to the next step. This effectively stops the processing of the
Event at the desired step in the chain.
The way this path is computed is done by an implementation of
EventDispatchChain and the EventTarget. An EventTarget must implement
the following method:
EventDispatchChain buildEventDispatchChain(EventDispatchChain tail);
When an Event is fired it has a designated EventTarget. Since the
EventTarget will be at the bottom of the scene-graph the chain is
built from the bottom up. It does this by prepending an EventDispatcher to the
EventDispatchChain at each level in the hierarchy (using
EventDispatchChain.prepend(EventDispatcher)). This is where EventDispatcher
starts to comes in.
Each EventTarget usually has its own EventDispatcher associated with it. The
implementations of EventTarget in standard JavaFX (Window, Scene, Node,
MenuItem, etc...) provide their own implementations of EventDispatcher. In
this regard you don't have to worry about how to use EventDispatcher. You don't
even use it directly. Rather, you add EventHandlers via the
[add|remove]EventHandler and [add|remove]EventFilter methods as well as
he various onXXX properties.
When buildEventDispatchChain is called on say, a Button, the Button prepends
its EventDispatcher to the given EventDispatchChain. It then calls
buildEventDispatchChain on its Parent if it has one. This continues up to
the root Node of the Scene. The root Node calls buildEventDispatchChain
on said Scene which, after prepending its EventDispatcher, does the same on
the Window it is attached to.
At this point the EventDispatchChain is fully built and is ready to process
the Event. If not obvious yet, the EventDispatchChain is fundamentally just
a "stack" of EventDispatchers. In other words, it is a highly specialized
java.util.Deque but without actually extending that interface.
Note: EventDispatchChain also provides an append(EventDispatcher) method
for the situations where prepending is the wrong operation.
Once the EventDispatchChain is fully built it is time to actually dispatch the
Event. This is done by calling this method on the EventDispatchChain:
Event dispatchEvent(Event event);
This has the EventDispatchChain get (pop) the first EventDispatcher on
the stack and call its method:
Event dispatchEvent(Event event, EventDispatchChain tail);
Side Note: Unfortunately EventDispatcher's method has a similar signature to
EventDispatchChain.dispatchEvent(Event) which may cause confusion.
Side Note #2: The tail will be the same EventDispatchChain throughout the
entire process.
This is where the EventDispatchers are actually used. Here is the algorithm
used by each EventDispatcher as defined by the internal
com.sun.javafx.event.BasicEventDispatcher class (Java 10 source code):
@Override
public Event dispatchEvent(Event event, final EventDispatchChain tail) {
event = dispatchCapturingEvent(event);
if (event.isConsumed()) {
return null;
}
event = tail.dispatchEvent(event);
if (event != null) {
event = dispatchBubblingEvent(event);
if (event.isConsumed()) {
return null;
}
}
return event;
}
The steps are:
Event for the capturing phase
EventHandlers that were added as a filterEvent here will not stop the dispatching of the Event in
this step; but will stop the Event from being processed in later stepsEvent is consumed, return null, else forward the Event to
tail and wait for it to returntail.dispatchEvent doesn't return null then dispatch the Event for
the bubbling phase
Event was null that means the Event was consumed
somewhere down the chain and no more processing is to be doneEventHandlers that where added as a handler (which
includes the ones added via the onXXX properties)Event here does not stop the processing of
this step; but will stop the Event from being processed in later stepsEvent is consumed return null, otherwise return the Event
EventDispatchChain, returning null means the Event has been
consumed and processing of the Event must stopThis is done for each EventDispatcher in the EventDispatchChain. The call
to tail.dispatchEvent is bascially a "recursive" operation (without it being
"real" recursion). In effect this code walks down the stack (the calls to
tail.dispatchEvent) and then walks back up the stack (when tail.dispatchEvent
returns). And each "link in the chain" does processing before the "recursive"
call (the capturing phase) and after the "recursive" call returns (the bubbling
phase).
Notice here, though, that at each step it is the EventDispatcher that is
actually invoking each of the appropriate EventHandlers. This is how an
EventDispatcher is used.
EventDispatcherWhen extending a class that already implements EventTarget then you should only
create your own EventDispatcher when absolutely needed. If your goal is to
control if an Event reaches a certain EventTarget then your first choice should
be to consume the Event at the appropriate place (mentioned by Jai in the
comments). If you want to alter something about the path of the Event then you
might need to provide your own EventDispatcher. However, due to the closed
nature of the internal EventDispatcher implementations, coupled with the fact
that the EventDispatcher interface is limited, you will probably be restricted
to wrapping the original EventDispatcher in your own implementation and delegating
when necessary. I've seen this done in other people's code (might have even seen
it in JavaFX itself) but I can't remember the code well enough to give you
examples of this.
If you are creating your own EventTarget from scratch then you will have to
implement your own EventDispatcher. Some things to keep in mind if you do
need your own implementation:
EventHandlers registered for the current phase (if
there are to be phases)EventHandlers registered for the Event's EventType
and said EventType's supertypesEvent to the tail EventDispatchChainnull if the Event has been consumedThread
EventHandler may remove itself or
add/remove another EventHandler while its handle method is executing. This
will take place while the EventDispatcher is iterating the EventHandlers
in some way.Event. In the standard JavaFX implementation
each EventDispatcher updates the source of the Event to be the Object
associated with that EventDispatcher (such as the Node). This is done in
subclasses of the above mentioned com.sun.javafx.event.BasicEventDispatcher.
The method to "fix the source" is Event.copyFor(Object, EventTarget)