Java/JavaFX企业级应用开发平台(fxEAP)
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

JavaFX事件机制

什么是事件

事件(Event)表示程序所感兴趣的事情的发生,比如鼠标的移动或者是某个按键的按下。 事件被用来将用户的动作通知给你的程序并且使程序能够响应该事件。JavaFX平台提供了捕获事件、跟踪事件、将事件路由到其目标和使程序能够按需处理事件的结构。 在JavaFX中,事件是javafx.event.Event类或其任何子类的实例。JavaFX提供了多种事件,包括DragEvent、KeyEvent、MouseEvent、ScrollEvent等。你可以通过继承Event类来实现你自己的事件。

JavaFX事件属性

属性 描述
事件类型(Event type) 发生事件的类型
源(Source) 事件的来源,表示该事件在事件派发链中的位置。事件通过派发链传递时,“源”会随之发生改变。
目标(Target) 发生动作的节点,在事件派发链的末尾。“目标”不会改变,但是如果某个事件过滤器在事件捕获阶段消费了该事件,“目标”将不会收到该事件。

事件子类提供了一些额外的信息,这些信息与事件的类型有关。例如,MouseEvent类包含哪个按钮被点击、按钮被点击的次数以及鼠标的位置等信息。

事件类型

事件类型(Event Type)是EventType类的实例。事件类型针对某个事件类型的多种事件进行了细化归类。例如,KeyEvent类包含如下事件类型:

  • KEY_PRESSED
  • KEY_RELEASED
  • KEY_TYPED

Event层级

J2001

JavaFX事件传递机制

J2001

从上往下传递。 由于路径上的Event Filter和Event Handler均会处理事件,因此路径可能会被修改。同样的,如果Event Filter或者Event Handler在任何时间点消费掉了事件,则在初始路径上的一些节点可能不会收到该事件。

Event Target

一个事件的目标可以是任何实现了EventTarget接口的类的实例。 buildEventDispatchChain方法的具体实现创建了事件派发链,事件必须经过该派发链到达事件目标。 Window、Scene和Node类均实现了EventTarget接口,这些类的子类也均继承了此实现。因此,在你的UI中的大多数元素都有它们已经定义好了的派发链,这使得你可以聚焦在如何响应事件上而不必关心创建事件派发链的事情。

事件分发过程

事件分发的步骤

  1. 选择目标
  2. 构造路径
  3. 捕获事件
  4. 事件冒泡

目标选择

  • 对于键盘事件,事件目标是已获取焦点的Node。
  • 对于鼠标事件,事件目标是光标所在位置处的Node;对于合成的(Synthesized)鼠标事件,触摸点被当做是光标所在位置。
  • 对于在触摸屏上产生的连续的手势事件,事件目标是手势开始时所有触碰位置的中心点处的Node。对于在非触摸屏(例如触控板)上产生的间接手势,事件目标是光标所在位置的Node。
  • 对于由在触摸屏上划动而产生的划动(swipe)事件,事件目标在所有手指的全部路径的中心处的Node。对于间接划动事件,事件目标是光标所在位置处的Node。
  • 对于触摸事件,每个触摸点的默认事件目标是第一次按下时所在位置处的Node。在Event Filter或者Event Handler中可通过ungrab()、grab()或者grab(Node)方法来为触摸点指定不同的事件目标。

构造路径

初始的事件路径是由事件派发链决定的,派发链是在被选中的事件目标的buildEventDispatchChain方法实现中创建的。当场景图中的一个节点被选中作为事件目标时,那么Node类的buildEventDispatchChain方法的默认实现中设置的初始事件路径即是从Stage到其自身的一条路径。

事件捕获

在事件捕获阶段,事件被程序的根节点派发并通过事件派发链向下传递到目标节点。 如果派发链中的任何节点为所发生的事件类型注册了Event Filter,则该Event Filter将会被调用。当Event filter执行完成以后,对应的事件会向下传递到事件派发链中的下一个节点。如果该节点未注册过滤器,事件将被传递到事件派发链中的下一个节点。如果没有任何过滤器消费掉事件,则事件目标最终将会接收到该事件并处理之。

事件冒泡

当到达事件目标并且所有已注册的过滤器都处理完事件以后,该事件将顺着派发链从目标节点返回到根节点。 如果在事件派发链中有节点为特定类型的事件注册了Event Handler,则在对应类型的事件发生时对应的Event Handler将会被调用。当Event Handler执行完成后,对应的事件将会向上传递给事件派发链中的上一个节点。如果么有任何Handler消费掉事件,则根节点最终将接收到对应的事件并且完成处理过程。

事件处理

事件处理功能由Event Filter和Event Handler提供,两者均是EventHandler接口的实现。如果你想要在某事件发生时通知应用程序,就需要为该事件注册一个Event Filter或Event Handler。Event Filter和Event Handler之间主要区别在于两者被执行的时机不同。

Handler

Event Handler在事件冒泡阶段执行。如果子节点的Event Handler未消耗掉对应的事件,那么父节点的Event Handler就可以在子节点处理完成以后来处理该事件,并且父节点的Event Handler还可以为多个子节点提供公共的事件处理过程。当某事件返回并经过注册了Event Handler的节点时,为该事件类型注册的Event Handler就会被执行。

一个节点可以注册多个Event Handler。Event Handler执行的顺序取决于事件类型的层级。特定事件类型的Event Handler会先于通用事件类型的Event Handler执行。例如,KeyEvent.KEY_TYPED事件的过滤器会在InputEvent.ANY事件的处理器执行。同层级的Event Handler的执行顺序并未指定。

Filter

Event Filter在事件捕获阶段执行。父节点的事件过滤器可以为多个子节点提供公共的事件处理,并且如果需要的话,也可以消费掉事件以阻止子节点收到该事件。当某事件被传递并经过注册了Event Filter的节点时,为该事件类型的注册的Event Filter就会被执行。 一个节点可以注册多个Event Filter。Event Filter执行的顺序取决于事件类型的层级关系。特定事件类型的Event Filter会先于通用事件类型的过滤器执行。例如,MouseEvent.MOUSE_PRESSED事件的Event Filter会在InputEvent.ANY事件的Event Filter之前执行。同层级的Event Filter的执行顺序并未指定。

事件消费

事件可以被Event Filter或 Event Handler在事件派发链中的任意节点上通过调用consume()方法消耗掉。该方法表示事件的处理已经完成,并且事件派发链的遍历也应该终止。 在Event Filter中消费掉对应的事件会阻止事件派发链上的任何子节点再响应该事件。在Event Handler中消费掉对应的事件会阻止事件派发链中的任何父处理器再处理该事件。但是,如果消费掉该事件的节点为该事件注册了多个Event Filter或者Event Handler,这些同级的Event Filter或者Event Handler仍然会被执行。