Yjuq
Yjuq

Reputation: 51

Custom Event Dispatcher - JavaFX

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

Answers (1)

Slaw
Slaw

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 EventHandlers 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 EventDispatchers. 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 EventHandlers. 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 EventHandlers though? Well, the internal implementation of EventDispatcher makes them a sort of "collection" of EventHandlers. 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 EventHandlers.

During the capturing phase the EventDispatcher invokes all the appropriate EventHandlers added via addEventFilter(EventType,EventHandler). Then, during the bubbling phase, the EventDispatcher invokes all the appropriate EventHandlers 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 EventHandlers 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 EventHandlers. Once that phase for that EventDispatcher has completed, however, the processing of the Event stops.

Whatever mechanism you use to store the EventHandlers 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 EventHandlers 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

Related Questions