Purpose of 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:
- If the
EventHandler
was registered for the current phase of the event
dispatching cycle (capturing or bubbling)
- If the
EventHandler
was registered for the current Event
's EventType
or
one of the supertypes (i.e. EventType.getSuperType()
)
Event Path (or "Chain")
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.
Capture Phase
Window
-> Scene
-> Root Node
-> Middle Node
-> EventTarget
Bubble Phase
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.
Dispatching The Event
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:
- Dispatch the
Event
for the capturing phase
- This invokes all the
EventHandler
s that were added as a filter
- Consuming an
Event
here will not stop the dispatching of the Event
in
this step; but will stop the Event
from being processed in later steps
- If the
Event
is consumed, return null
, else forward the Event
to
tail
and wait for it to return
- If
tail.dispatchEvent
doesn't return null
then dispatch the Event
for
the bubbling phase
- If the returned
Event
was null
that means the Event
was consumed
somewhere down the chain and no more processing is to be done
- This invokes all the
EventHandler
s that where added as a handler (which
includes the ones added via the onXXX
properties)
- As with step #1, consuming an
Event
here does not stop the processing of
this step; but will stop the Event
from being processed in later steps
- If the
Event
is consumed return null
, otherwise return the Event
- As with
EventDispatchChain
, returning null
means the Event
has been
consumed and processing of the Event
must stop
This 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.
Implementing Your Own 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:
- It must only invoke the
EventHandler
s registered for the current phase (if
there are to be phases)
- It must only invoke the
EventHandler
s registered for the Event
's EventType
and said EventType
's supertypes
- It must forward the
Event
to the tail EventDispatchChain
- It must only return
null
if the Event
has been consumed
- It must be capable of concurrent modification by the same
Thread
- The reason for this is because an
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.
- It must "fix the source" of the
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)
- Note: I'm not sure if this is actually part of the contract and may not be
necessary if your implementation doesn't require it.