JavaFX-EventDispatcher和EventFilter之间的区别

Sai*_*dem 1 javafx event-handling

我试图了解何时在JavaFX中使用EventDispatcher。我非常了解捕获和冒泡的所有事件机制。但是我仍然对EventDispatcher的目的感到困惑。因为我可以使用过滤器和处理程序来完成大部分工作。

有人可以解释一下实际目的是什么以及如何使用此EventDispatcher吗?

谢谢。

Sla*_*law 7

目的 EventDispatcher

顾名思义,an的目的EventDispatcher是调度an Event。其执行方式取决于实现方式。但是,标准实现(JavaFX内部)构成EventDispatcher的一种“集合” EventHandler。负责在正确的时间EventDispatcher 调用适当EventHandler的命令。什么使EventHandler“适当”取决于:

  • 如果在EventHandler事件分发周期的当前阶段(捕获或冒泡)注册了。
  • 如果EventHandler被注册为当前EventEventType或超类型的一个(即EventType.getSuperType()

事件路径(或“链”)

如果我们专注于场景图,Event则触发an时,它会从场景图的顶部(Window)开始,并遍历层次结构到达目标(通常是a Node,但至少是的实现 EventTarget)。这是事件分发周期的“捕获”阶段。到达目标后,它会返回场景图,直到Window再次到达 。这是周期的“起泡”阶段。

捕获阶段

  • Window-> Scene-> Root Node-> Middle Node->EventTarget

气泡阶段

  • Window<- Scene<- Root Node<- Middle Node<-EventTarget

如果在任何步骤Event(通过Event.consume())都消耗了它,那么它将不会转发到下一步。这有效地停止Event了链中所需步骤的处理 。

这条路径的计算方式是通过实现做 EventDispatchChainEventTarget。一个EventTarget必须实现以下方法:

EventDispatchChain buildEventDispatchChain(EventDispatchChain tail);
Run Code Online (Sandbox Code Playgroud)

Event触发时,它有一个指定的EventTarget。由于 EventTarget它将位于场景图的底部,因此链是从下向上构建的。它通过在层次结构的每个级别前面添加一个EventDispatcher来 完成此操作EventDispatchChain(使用 EventDispatchChain.prepend(EventDispatcher))。这是EventDispatcher 开始出现的地方。

每个EventTarget通常都有自己的EventDispatcher关联。的实现EventTarget在标准的JavaFX( ,WindowSceneNodeMenuItem等...)提供自己的实现EventDispatcher。在这方面,您不必担心如何使用EventDispatcher。您甚至都不直接使用它。相反,您可以EventHandler通过 [add|remove]EventHandler[add|remove]EventFilter方法以及的各种onXXX属性来添加。

buildEventDispatchChain被要求说a时ButtonButton将其附加EventDispatcher到给定的EventDispatchChain。然后buildEventDispatchChainParent如果有一个,则调用 它的。这将继续到根NodeScene。根Node调用buildEventDispatchChain 说的东西Scene,在它的前面加上EventDispatcher,在Window它所连接的对象上做同样的事情。

至此,EventDispatchChain已完全构建好并准备处理Event。如果尚不明显,EventDispatchChain则从根本上讲只是的“堆栈” EventDispatcher。换句话说,它是高度专业化的, java.util.Deque但实际上并未扩展该接口。

注意:EventDispatchChain还提供了一种append(EventDispatcher)方法,用于在前置操作错误的情况下进行操作


调度事件

一旦EventDispatchChain完全构建,就该真正地调度了 Event。这是通过在上调用此方法来完成的EventDispatchChain

Event dispatchEvent(Event event);
Run Code Online (Sandbox Code Playgroud)

它具有第一个EventDispatchChainget(pop)EventDispatcher栈,并调用方法:

Event dispatchEvent(Event event, EventDispatchChain tail);
Run Code Online (Sandbox Code Playgroud)

旁注:不幸EventDispatcher的是,该方法的签名类似 EventDispatchChain.dispatchEvent(Event),可能会引起混淆

旁注2:在整个过程中都tail相同EventDispatchChain

这是EventDispatcher实际使用s的地方。这是EventDispatcher内部com.sun.javafx.event.BasicEventDispatcher类定义 的每个算法所使用的算法(Java 10源代码):

@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;
}
Run Code Online (Sandbox Code Playgroud)

这些步骤是:

  1. Event为捕获阶段 调度
    • 这将调用所有EventHandler作为过滤器添加的
    • Event此消费an 不会停止Event 在此步骤中的调度; 但将Event在以后的步骤中停止处理
  2. 如果Event已消耗,则返回null,否则将转发Eventtail并等待其返回
  3. 如果tail.dispatchEvent没有返回,null则分派Event冒泡阶段
    • 如果返回的Eventnull,则表示Event已在链下某个位置消耗了该货币,并且不再需要进行其他处理
    • 这将调用EventHandler添加为处理程序的所有s (包括通过onXXX属性添加的)。
    • 与步骤1一样,在Event这里消费an 不会停止该步骤的处理;但将Event在以后的步骤中停止处理
  4. 如果Event消耗了null,则返回,否则返回。Event
    • 与一样EventDispatchChain,返回null意味着Event已经消耗掉并且Event必须停止处理

这是针对中的每个EventDispatcherEventDispatchChain对象完成tail.dispatchEvent基本上,对的调用是“递归”操作(而不是“真实”递归)。实际上,此代码沿堆栈向下移动(对的调用 tail.dispatchEvent),然后沿堆栈向上移动(tail.dispatchEvent 返回时)。在“递归”调用之前(捕获阶段)和“递归”调用返回之后(冒泡阶段),每个“链中的链接”都进行处理。

但是请注意,在每个步骤中EventDispatcher,实际上是在调用每个适当EventHandler的。这是使用an的 EventDispatcher方式


实施自己的 EventDispatcher

扩展已经实现的类时,EventTarget仅应EventDispatcher绝对需要时创建自己的类。如果您的目标是控制an是否Event达到某个确定值,EventTarget那么您的第一选择应该是Event在适当的地方食用该饮料(Jai在评论中提到)。如果您想更改的路径,Event则可能需要提供自己的EventDispatcher。但是,由于内部EventDispatcher实现的封闭性,再加上EventDispatcher接口受限制的事实,您很可能只能包装原始文件。EventDispatcher在您自己的实现中,并在必要时委派。我已经在其他人的代码中看到了这一点(甚至可能在JavaFX本身中也看到了),但是我记不清代码的程度,无法为您提供示例。

如果要从头开始创建自己的文件 EventTarget,则必须实现自己的文件EventDispatcher。如果确实需要自己的实现,请记住以下几点:

  • 它只能调用EventHandler为当前阶段注册的s(如果有阶段)
  • 它只能调用EventHandler注册为氏EventEventType ,说EventType的超类型
  • 它必须Event将尾巴向前EventDispatchChain
  • 它必须只返回nullEvent已消耗
  • 它必须能够同时进行修改Thread
    • 这样做的原因是因为an EventHandler可能会EventHandlerhandle执行其方法时删除自己或添加/删除另一个。这将EventDispatcherEventHandlers以某种方式迭代s时发生。
  • 它必须“修复”的来源Event。在标准JavaFX实现中,每个EventDispatcher更新Event都将Object 与其关联的源EventDispatcher(例如Node)。这是在上述子类中完成的com.sun.javafx.event.BasicEventDispatcher。“修复源”的方法是Event.copyFor(Object, EventTarget)
    • 注意:我不确定这是否实际上是合同的一部分,如果您的实现不需要它,则可能没有必要。