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
EventDispatcher
As 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 EventHandler
s. 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
-> EventTarget
Window
<- Scene
<- Root Node
<- Middle Node
<- EventTarget
If 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 EventHandler
s 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 EventDispatcher
s. 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 EventDispatcher
s 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
EventHandler
s 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 doneEventHandler
s 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 EventHandler
s. This is how an
EventDispatcher
is used.
EventDispatcher
When 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:
EventHandler
s registered for the current phase (if
there are to be phases)EventHandler
s registered for the Event
's EventType
and said EventType
's supertypesEvent
to the tail EventDispatchChain
null
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 EventHandler
s
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)