3 使用事件过滤器
本主题描述了JavaFX应用程序中的事件过滤器。了解如何使用事件过滤器来处理由键盘操作、鼠标操作、滚动操作和其他用户与应用程序的交互所生成的事件。
事件过滤器使您能够在事件处理的捕获阶段处理事件。一个节点可以有一个或多个用于处理事件的过滤器。一个过滤器可以用于多个节点和多个事件类型。事件过滤器使得父节点能够为其子节点提供公共处理,或者拦截事件并阻止子节点对事件的操作。
注册和移除事件过滤器
要在事件捕获阶段处理事件,节点必须注册一个事件过滤器。事件过滤器是EventHandler
接口的实现。该接口的handle()
方法提供了当与过滤器关联的事件被注册过滤器的节点接收到时执行的代码。
要注册一个过滤器,使用addEventFilter()
方法。该方法接受事件类型和过滤器作为参数。在示例3-1中,第一个过滤器被添加到一个单独的节点,并处理特定的事件类型。定义了一个处理输入事件的第二个过滤器,并由两个不同的节点注册。同样的过滤器也被注册到两种不同类型的事件上。
示例3-1 注册过滤器
// 为单个节点和特定的事件类型注册事件过滤器 node.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() { public void handle(MouseEvent) { ... }; }); // 定义一个事件过滤器 EventHandler filter = new EventHandler(<InputEvent>() { public void handle(InputEvent event) { System.out.println("过滤掉事件 " + event.getEventType()); event.consume(); } // 为两个不同的节点注册相同的过滤器 myNode1.addEventFilter(MouseEvent.MOUSE_PRESSED, filter); myNode2.addEventFilter(MouseEvent.MOUSE_PRESSED, filter); // 为另一种事件类型注册过滤器 myNode1.addEventFilter(KeyEvent.KEY_PRESSED, filter);
请注意,为一种事件类型定义的事件过滤器也可以用于该事件的任何子类型。有关事件类型层次结构的信息,请参见事件类型。
当您不再希望事件过滤器处理节点或事件类型的事件时,请使用removeEventFilter()
方法移除过滤器。该方法接受事件类型和过滤器作为参数。在示例3-2中,从myNode1
的MouseEvent.MOUSE_PRESSED
事件中移除了示例3-1中定义的过滤器。该过滤器仍然会被myNode2
和myNode1
在KeyEvent.KEY_PRESSED
事件中执行。
使用事件过滤器
事件过滤器通常用于事件分发链的分支节点,并在事件处理的捕获阶段调用。使用过滤器执行操作,例如覆盖事件响应或阻止事件到达目标。
要查看过滤器的使用示例,请下载DraggablePanelsExample.zip
文件。解压缩NetBeans项目并在NetBeans IDE中打开。以下部分描述了此示例使用的过滤器。
可拖动面板示例
可拖动面板示例演示了以下过滤器的使用:
-
为超类型事件注册过滤器,以提供子类型事件的通用处理
-
消费事件以防止子节点对其进行操作
图3-1是启动可拖动面板示例时显示的屏幕。用户界面由三个面板组成。每个面板包含不同的UI控件。屏幕底部有一个复选框,用于控制是否可以拖动面板。
如果未选中复选框,则单击面板中的任何控件会生成响应。如果选中复选框,则各个控件不会响应鼠标点击。相反,单击面板内任何位置并拖动鼠标会移动整个面板,从而可以更改面板的位置,如图3-2所示。
可拖动面板示例的过滤器
在可拖动面板示例中,makeDraggable()
方法用于创建三个面板,使每个面板可移动。该方法和过滤器定义如示例3-3所示。
示例3-3 makeDraggable()中的过滤器定义
private Node makeDraggable(final Node node) { final DragContext dragContext = new DragContext(); final Group wrapGroup = new Group(node); wrapGroup.addEventFilter( MouseEvent.ANY, new EventHandler<MouseEvent>() { public void handle(final MouseEvent mouseEvent) { if (dragModeActiveProperty.get()) { // 禁用所有子节点的鼠标事件 mouseEvent.consume(); } } }); wrapGroup.addEventFilter( MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() { public void handle(final MouseEvent mouseEvent) { if (dragModeActiveProperty.get()) { // 记录初始鼠标光标坐标和节点位置 dragContext.mouseAnchorX = mouseEvent.getX(); dragContext.mouseAnchorY = mouseEvent.getY(); dragContext.initialTranslateX = node.getTranslateX(); dragContext.initialTranslateY = node.getTranslateY(); } } }); wrapGroup.addEventFilter( MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { public void handle(final MouseEvent mouseEvent) { if (dragModeActiveProperty.get()) { // 根据鼠标光标移动计算的增量,将节点从初始位置移动 node.setTranslateX( dragContext.initialTranslateX + mouseEvent.getX() - dragContext.mouseAnchorX); node.setTranslateY( dragContext.initialTranslateY + mouseEvent.getY() - dragContext.mouseAnchorY); } } }); return wrapGroup; }
为每个面板定义并注册了以下事件的过滤器:
-
MouseEvent.ANY
。该过滤器处理面板的所有鼠标事件。如果选择了拖动模式复选框,则过滤器会消耗事件,面板内的子节点(即面板内的UI控件)不会接收到事件。如果未选择复选框,则鼠标光标所在位置的控件会处理事件。 -
MouseEvent.MOUSE_PRESSED
。该过滤器仅处理面板的鼠标按下事件。如果选择了拖动模式复选框,则会存储鼠标的当前位置。 -
MouseEvent.MOUSE_DRAGGED
。该过滤器仅处理面板的鼠标拖动事件。如果选择了拖动模式复选框,则会移动面板。
请注意,一个面板有三个注册的过滤器。特定事件类型的过滤器在超类型事件之前被调用,因此MouseEvent.MOUSE_PRESSED
和MouseEvent.MOUSE_DRAGGED
的过滤器会在MouseEvent.ANY
的过滤器之前被调用。