首页 > 解决方案 > 自定义事件调度程序 - JavaFX

问题描述

有谁知道如何基于 javafx.event 包编写自定义事件调度程序?我在 Google & Co. 中进行了搜索,但没有找到一个很好的例子。

有没有人给我一个简约的例子?那会很好——我试了几次来理解它,但我失败了。

标签: eventsjavafx

解决方案


首先要了解的是 JavaFX 如何分派事件。

当 anEvent被触发时,它有一个关联的EventTarget. 如果目标在场景图中,则路径从Event开始Window并沿着场景图向下直到EventTarget到达。然后返回场景图,直到Event再次到达。这分别称为“捕获阶段”和“冒泡阶段”。在捕获阶段调用事件过滤器,在冒泡阶段调用事件处理程序。使用属性(例如)设置的s是特殊类型的处理程序(即不是过滤器)。WindowEventHandleronXXXonMouseClicked

EventDispatcher接口有以下方法:

public Event dispatchEvent(Event event, EventDispatChain tail) { ... }

在这里,eventEvent被调度的,而tail是由EventDispatchChain构建的,可能是递归的EventTarget.buildEventDispatchChain(EventDispatchChain)null如果在event方法执行期间被消耗,这将返回。

EventDispatchChain一堆EventDispatchers 。_ 每次你打电话时,你基本上都是从顶部tail.dispatchEvent(event)弹出并调用它。EventDispatcher

@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;
}

您可能想知道在哪里定义dispatchCapturingEventdispatchBubblingEvent定义。这些方法将由您创建并调用适当EventHandler的 s。您可能还想知道为什么这些方法返回一个Event. 原因很简单:在这些方法的处理过程中,Event连同tail.dispatchEvent,可能会改变Event. consume()但是,除了 之外,Event它的子类基本上是不可变的。这意味着任何其他更改都需要创建一个新的Event. 事件处理过程的其余部分应该使用这个新的。 Event

调用tail.dispatchEvent几乎总是会返回一个新的Event. 这是因为 中的每个EventDispatcher通常都EventDispatchChain与自己的相关联(例如 aLabelWindow)。当 anEventHandler被调用时, 的来源Event 必须Object与注册的相同EventHandler;如果 anEventHandler在 a 中注册,Windowevent.getSource()必须Window在 saidEventHandler执行期间返回它。实现这一点的方法是使用该Event.copyFor(Object,EventTarget)方法。

Event oldEvent = ...;
Event newEvent = oldEvent.copyFor(newSource, oldEvent.getTarget());

如您所见,EventTarget通常始终保持不变。此外,子类可能会覆盖copyFor,而其他的,例如MouseEvent,也可能会定义重载。

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.


推荐阅读