Reputation: 51
Does anyone know how to write a custom Event Dispatcher based on the javafx.event package? I searched in Google & Co. but didn't find a nice example.
Have anyone a minimalistic example for me? That would be nice - I tried it a few times to understand it, but I failed.
Upvotes: 0
Views: 1754
Reputation: 45736
The first thing to realize is how JavaFX dispatches events.
When an Event
is fired it has an associated EventTarget
. If the target was in the scene-graph then the path of the Event
starts at the Window
and goes down the scene-graph until the EventTarget
is reached. The Event
then goes back up the scene-graph until it reaches the Window
again. This is known as the "capturing phase" and the "bubbling phase", respectively. Event filters are invoked during the capturing phase and event handlers are invoked during the bubbling phase. The EventHandler
s set using the onXXX
properties (e.g. onMouseClicked
) are special types of handlers (i.e. not filters).
The EventDispatcher
interface has the following method:
public Event dispatchEvent(Event event, EventDispatChain tail) { ... }
Here, the event
is the Event
being dispatched and the tail
is the EventDispatchChain
built, possibly recursively, by EventTarget.buildEventDispatchChain(EventDispatchChain)
. This will return null
if the event
is consumed during execution of the method.
The EventDispatchChain
is a stack of EventDispatcher
s. Every time you call tail.dispatchEvent(event)
you are essentially popping an EventDispatcher
off the top and invoking it.
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
// First, dispatch event for the capturing phase
event = dispatchCapturingEvent(event);
if (event.isConsumed()) {
// One of the EventHandlers invoked in dispatchCapturingEvent
// consumed the event. Return null to indicate processing is complete
return null;
}
// Forward the event to the next EventDispatcher in the chain
// (i.e. on the stack). This will start the "capturing" on the
// next EventDispatcher. Returns null if event was consumed down
// the chain
event = tail.dispatchEvent(event);
// once we've reached this point the capturing phase has completed
if (event != null) {
// Not consumed from down the chain so we now handle the
// bubbling phase of the process
event = dispatchBubblingEvent(event);
if (event.isConsumed()) {
// One of the EventHandlers invoked in dispatchBubblingEvent
// consumed the event. Return null to indicate processing is complete
return null;
}
}
// return the event, or null if tail.dispatchEvent returned null
return event;
}
You're probably wondering where dispatchCapturingEvent
and dispatchBubblingEvent
are defined. These methods would be created by you and would invoke the appropriate EventHandler
s. You might also be wondering why these methods return an Event
. The reason is simple: During the processing of the Event
these methods, along with tail.dispatchEvent
, might alter the Event
. Other than consume()
, however, Event
and its subclasses are basically immutable. This means any other alterations require the creation of a new Event
. It is this new Event
that should be used by the rest of the event-handling process.
The call to tail.dispatchEvent
will virtually always return a new instance of the Event
. This is due to the fact each EventDispatcher
in the EventDispatchChain
is normally associated with its own source (e.g. a Label
or Window
). When an EventHandler
is being invoked the source of the Event
must be the same Object
that the EventHandler
was registered to; if an EventHandler
was registered with a Window
then event.getSource()
must return that Window
during said EventHandler
's execution. The way this is achieved is by using the Event.copyFor(Object,EventTarget)
method.
Event oldEvent = ...;
Event newEvent = oldEvent.copyFor(newSource, oldEvent.getTarget());
As you can see, the EventTarget
normally remains the same throughout. Also, subclasses may override copyFor
while others, such as MouseEvent
, may also define an overload.
How are the events actually dispatched to the EventHandler
s though? Well, the internal implementation of EventDispatcher
makes them a sort of "collection" of EventHandler
s. Each EventDispatcher
tracks all filters, handlers, and property-handlers (onXXX
) that have been added to or removed from its associated source (e.g. Node
). Your EventDispatcher
doesn't have to do this but it will need a way to access wherever you do store the EventHandler
s.
During the capturing phase the EventDispatcher
invokes all the appropriate EventHandler
s added via addEventFilter(EventType,EventHandler)
. Then, during the bubbling phase, the EventDispatcher
invokes all the appropriate EventHandler
s added via addEventHandler(EventType,EventHandler)
or setOnXXX
(e.g. setOnMouseClicked
).
What do I mean by appropriate?
Every fired Event
has an associated EventType
. Said EventType
may have a super EventType
. For instance, the "inheritance" tree of MouseEvent.MOUSE_ENTERED
is:
Event.ANY
InputEvent.ANY
MouseEvent.ANY
MouseEvent.MOUSE_ENTERED_TARGET
MouseEvent.MOUSE_ENTERED
When dispatching an Event
you have to invoke all the EventHandler
s registered for the Event
's EventType
and all the EventType
's supertypes. Also, note that consuming an Event
does not stop processing of that Event
for the current phase of the current EventDispatcher
but instead finishes invoking all appropriate EventHandler
s. Once that phase for that EventDispatcher
has completed, however, the processing of the Event
stops.
Whatever mechanism you use to store the EventHandler
s must be capable of concurrent modification by the same thread. This is because an EventHandler
may add or remove another EventHandler
to or from the same source for the same EventType
for the same phase. If you stored them in a regular List
this means the List
may be modified while you're iterating it. A readily available example of an EventHandler
that may remove itself is WeakEventHandler
. A WeakEventHandler
will attempt to remove itself if it is invoked after it has been "garbage collected".
Also, I don't know if this is required, but the internal implementation doesn't allow the same EventHandler
to be registered more than once for the same source, EventType
, and phase. Remember, though, that the EventHandler
s added via addEventHandler
and those added via setOnXXX
are handled separately even though they are both invoked during the same phase (bubbling). Also, calling setOnXXX
replaces any previous EventHandler
set for the same property.
Upvotes: 8