Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
307 views
in Technique[技术] by (71.8m points)

event handling - JavaFX - Difference between EventDispatcher and EventFilter

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...