2

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 the purpose of EventDispatcher. Because I can get most of my work done using filters & handlers.

Can someone please explain what is the actual purpose and how to use this EventDispatcher?

Thank You.

Sai Dandem
  • 8,229
  • 11
  • 26
  • Event dispatcher is usually done by control makers (i.e. API makers). That is the part that actually determines how capturing and bubbling is done. Most of the things that involve events are subclasses of `Node` class, and that class has done the dispatcher for you, so you do not need to do that. Also note that things like `MenuItem` do not extend from `Node` but implements `EventTarget`, so they are some custom event routing there. Unless you are doing something similar to that, there is no need to understand all these. – Jai Jun 25 '18 at 02:28
  • @Jai Thanks for the very quick brief explanation. – Sai Dandem Jun 25 '18 at 03:32

1 Answers1

10

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 EventHandlers. 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 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.


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 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:

  1. Dispatch the Event for the capturing phase
    • This invokes all the EventHandlers 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
  2. If the Event is consumed, return null, else forward the Event to tail and wait for it to return
  3. 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 EventHandlers 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
  4. 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 EventHandlers. 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 EventHandlers registered for the current phase (if there are to be phases)
  • It must only invoke the EventHandlers 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 EventHandlers 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.
Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Note: This answer is sort of a mess (it was basically stream-of-consciousness) and I might edit it to make it more organized and clear later. – Slaw Jun 25 '18 at 03:17
  • I think it's a good starting step for beginners to understand how JavaFX processes events. – Jai Jun 25 '18 at 03:40
  • @Slaw, Thank you for the very detailed explanation.:) My main requirement is to add some customisation to existing controls. So I am extending the controls and adding my additional features. My main confusion is to whether add the event customisation(s) through filters/handlers or eventDispatcher. Based on your explanation I think there is no need for me to consider EventDispatcher, as I want to customize some behaviour changes only. – Sai Dandem Jun 25 '18 at 04:31
  • @SaiDandem Yup. Do dispatch stuff only when you need to route the event in a completely different manner. If you just want to block events, just consume those events at the correct place. – Jai Jun 25 '18 at 04:38
  • @Jai, Thanks for confirming. – Sai Dandem Jun 25 '18 at 04:43
  • @SaiDandem I'm glad I could be of help :) Also, I went ahead with the edit I said I might do. I believe I covered everything from the original answer and most likely added more information. However, since you have already accepted the answer, let me know if I removed something (or otherwise) that somehow makes the answer invalid/incomplete. – Slaw Jun 25 '18 at 05:44
  • @Slaw, this looks like the complete anatomy of EventDispatcher ;) I can't expect a more detailed explanation than this. I would suggest to post the answer as a separate blog post so that it can reach more people. Thanks a Ton!! for your effort in explaining. – Sai Dandem Jun 25 '18 at 23:56
  • Do you have an actual example of such an implementation? The EventHandlerManager that I used in JDK 8 has been removed and I'm struggling on what to use. Basically, I need my own EventDispatcher but it seems like a tough piece considering the listing of requirements you made. – Maxoudela Jan 03 '19 at 15:46
  • @Maxoudela Take a look at [this](https://gitlab.com/snippets/1795870). I can't guarantee the implementation is flawless as I wrote it quickly and haven't tested it. – Slaw Jan 08 '19 at 11:03
  • First of all thank you for such a detailed answer - this is a great work. Can you say, what is the entry point of event handling. For example, I have a `Button` and I have and object `Event(source, target, type)`. Is the entry point `button.fireEvent(event)`? – Pavel_K Jan 21 '19 at 08:44
  • @Slaw Or I am absolutely wrong? I will say why I need this - I want to make simple javafx implementation for web. – Pavel_K Jan 22 '19 at 10:37
  • @Pavel_K `Node.fireEvent(Event)` is essentially a convenience method for the static `Event.fireEvent(EventTarget,Event)` method where the `EventTarget` is the `Node`. This will cause the `EventTarget` to be the `Node` regardless of the `EventTarget` the `Event` was constructed with. So if you want to fire an `Event` with `button` as the target then yes, `button.fireEvent` is the entry point. – Slaw Jan 22 '19 at 13:44
  • @Maxoudela wondering what you consider changed: EventHandlerManager was internal api in fx8 and still is internal api in fx9+ or not? – kleopatra Sep 15 '19 at 12:46
  • @kleopatra The com.sun.javafx.event.EventHandlerManager was indeed internal API in FX8 but was accessible. In FX9+, the com.sun API are no longer accessible at all if I recall – Maxoudela Sep 16 '19 at 13:11
  • @Maxoudela currently, it still is - if you modify modularization by --add-export – kleopatra Sep 16 '19 at 13:58
  • Hum I see, it means I am missing the com.sun import somehow? – Maxoudela Sep 17 '19 at 14:38