如何发出和处理自定义事件?

2022-09-01 10:24:02

javafx 中有几个预定义的事件类。Event.ANY, KeyEvent.KEY_TYPED, MouseEvent.ANY 等等。还有针对事件的高级过滤和处理系统。我想重用它来发送一些自定义信号。

如何创建自定义事件类型 CustomEvent.Any,以编程方式发出此事件并在节点中处理它?


答案 1

通常:

  1. 创建所需的事件类型
  2. 创建相应的事件
  3. 调用 Node.fireEvent().
  4. 为感兴趣的事件类型添加处理程序和/或筛选器

一些解释:

如果要创建事件级联,请从“全部”或“任何”类型开始,这将是所有事件类型的根:

EventType<MyEvent> OPTIONS_ALL = new EventType<>("OPTIONS_ALL");

这样就可以创建此类型的后代:

EventType<MyEvent> BEFORE_STORE = new EventType<>(OPTIONS_ALL, "BEFORE_STORE");

然后编写类(扩展 )。应将 EventTypes 键入到此事件类(如我的示例所示)。MyEventEvent

现在使用(或换句话说:fire)事件:

Event myEvent = new MyEvent();
Node node = ....;
node.fireEvent(myEvent);

如果要捕获此事件:

Node node = ....;
node.addEventHandler(OPTIONS_ALL, event -> handle(...));
node.addEventHandler(BEFORE_STORE, event -> handle(...));

答案 2

下面是一个(稍微过于复杂)的示例应用程序,演示了 eckig 在他的(优秀)答案中概述的一些概念。

该示例创建了一个视野,该视野是反应堆节点的平铺窗格。自定义闪电事件定期发送到随机节点,该节点在收到事件时将闪烁黄色。筛选器和处理程序将添加到父字段,并将其调用报告给 system.out,以便您可以看到事件冒泡和捕获操作中的阶段。

LightningEvent 本身的代码主要是直接从 JavaFX 源代码中的标准 ActionEvent 代码复制的,您的事件代码可能会更简单一些。

enter image description here

import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.Random;

public class LightningSimulator extends Application {
    private static final int FIELD_SIZE = 10;

    private static final Random random = new Random(42);

    @Override
    public void start(Stage stage) throws Exception {
        TilePane field = generateField();

        Scene scene = new Scene(field);
        stage.setScene(scene);
        stage.setResizable(false);
        stage.show();

        field.addEventFilter(
                LightningEvent.PLASMA_STRIKE,
                event -> System.out.println(
                        "Field filtered strike: " + event.getI() + ", " + event.getJ()
                )
        );

        field.addEventHandler(
                LightningEvent.PLASMA_STRIKE,
                event -> System.out.println(
                        "Field handled strike: " + event.getI() + ", " + event.getJ()
                )
        );

        periodicallyStrikeRandomNodes(field);
    }

    private void periodicallyStrikeRandomNodes(TilePane field) {
        Timeline timeline = new Timeline(
                new KeyFrame(
                        Duration.seconds(0),
                        event -> strikeRandomNode(field)
                ),
                new KeyFrame(
                        Duration.seconds(2)
                )
        );

        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();
    }

    private void strikeRandomNode(TilePane field) {
        LightningReactor struckNode = (LightningReactor)
                field.getChildren()
                        .get(
                                random.nextInt(
                                        FIELD_SIZE * FIELD_SIZE
                                )
                        );
        LightningEvent lightningStrike = new LightningEvent(
                this,
                struckNode
        );

        struckNode.fireEvent(lightningStrike);
    }

    private TilePane generateField() {
        TilePane field = new TilePane();
        field.setPrefColumns(10);
        field.setMinWidth(TilePane.USE_PREF_SIZE);
        field.setMaxWidth(TilePane.USE_PREF_SIZE);

        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                field.getChildren().add(
                        new LightningReactor(
                                i, j,
                                new StrikeEventHandler()
                        )
                );
            }
        }
        return field;
    }

    private class LightningReactor extends Rectangle {
        private static final int SIZE = 20;
        private final int i;
        private final int j;

        private FillTransition fillTransition = new FillTransition(Duration.seconds(4));

        public LightningReactor(int i, int j, EventHandler<? super LightningEvent> lightningEventHandler) {
            super(SIZE, SIZE);

            this.i = i;
            this.j = j;

            Color baseColor =
                    (i + j) % 2 == 0
                            ? Color.RED
                            : Color.WHITE;
            setFill(baseColor);

            fillTransition.setFromValue(Color.YELLOW);
            fillTransition.setToValue(baseColor);
            fillTransition.setShape(this);

            addEventHandler(
                    LightningEvent.PLASMA_STRIKE,
                    lightningEventHandler
            );
        }

        public void strike() {
            fillTransition.playFromStart();
        }

        public int getI() {
            return i;
        }

        public int getJ() {
            return j;
        }
    }

    private class StrikeEventHandler implements EventHandler<LightningEvent> {
        @Override
        public void handle(LightningEvent event) {
            LightningReactor reactor = (LightningReactor) event.getTarget();
            reactor.strike();

            System.out.println("Reactor received strike: " + reactor.getI() + ", " + reactor.getJ());


            // event.consume();  if event is consumed the handler for the parent node will not be invoked.
        }
    }

    static class LightningEvent extends Event {

        private static final long serialVersionUID = 20121107L;

        private int i, j;

        public int getI() {
            return i;
        }

        public int getJ() {
            return j;
        }

        /**
         * The only valid EventType for the CustomEvent.
         */
        public static final EventType<LightningEvent> PLASMA_STRIKE =
                new EventType<>(Event.ANY, "PLASMA_STRIKE");

        /**
         * Creates a new {@code LightningEvent} with an event type of {@code PLASMA_STRIKE}.
         * The source and target of the event is set to {@code NULL_SOURCE_TARGET}.
         */
        public LightningEvent() {
            super(PLASMA_STRIKE);
        }

        /**
         * Construct a new {@code LightningEvent} with the specified event source and target.
         * If the source or target is set to {@code null}, it is replaced by the
         * {@code NULL_SOURCE_TARGET} value. All LightningEvents have their type set to
         * {@code PLASMA_STRIKE}.
         *
         * @param source    the event source which sent the event
         * @param target    the event target to associate with the event
         */
        public LightningEvent(Object source, EventTarget target) {
            super(source, target, PLASMA_STRIKE);

            this.i = ((LightningReactor) target).getI();
            this.j = ((LightningReactor) target).getJ();
        }

        @Override
        public LightningEvent copyFor(Object newSource, EventTarget newTarget) {
            return (LightningEvent) super.copyFor(newSource, newTarget);
        }

        @Override
        public EventType<? extends LightningEvent> getEventType() {
            return (EventType<? extends LightningEvent>) super.getEventType();
        }

    }

}