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

时间:2021-07-18 23:54:47

There are several predefined event classes in javafx. Event.ANY, KeyEvent.KEY_TYPED, MouseEvent.ANY and so on. There is also advanced filtering and handling system for events. And I'd like to reuse it to send some custom signals.

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

How can I create my custom event type CustomEvent.Any, emit this event programmatically and handle it in a node?

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

2 个解决方案

#1


35  

In general:

  1. Create a desired EventType.
  2. 创建所需的EventType。

  3. Create the corresponding Event.
  4. 创建相应的事件。

  5. Call Node.fireEvent().
  6. Add Handlers and/or Filters for EventTypes of interest.
  7. 为感兴趣的EventType添加处理程序和/或过滤器。

Some explanations:

If you want to create an event cascade, start with an "All" or "Any" type, that will be the root of all of the EventTypes:

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

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

This makes possible creating descendants of this type:

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

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

Then write the MyEvent class (which extends Event). The EventTypes should be typed to this event class (as is my example).

然后编写MyEvent类(扩展Event)。应该为此事件类键入EventTypes(就像我的示例一样)。

Now use (or in other words: fire) the event:

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

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

If you want to catch this event:

如果你想抓住这个事件:

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

#2


18  

Here is a (slightly over-complicated) sample application demonstrating some of the concepts that eckig outlines in his (excellent) answer.

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

The sample creates a visual field which is a tiled pane of reactor nodes. A custom lightning event is periodically sent to a random node, which will flash yellow when it receives the event. Filters and handlers are added to the parent field and their invocation reported to system.out so that you can see the event bubbling and capturing phases in action.

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

The code for the LightningEvent itself was mainly copied directly from the standard ActionEvent code in the JavaFX source, your event code could probably be a little simpler.

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

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

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();
        }

    }

}

#1


35  

In general:

  1. Create a desired EventType.
  2. 创建所需的EventType。

  3. Create the corresponding Event.
  4. 创建相应的事件。

  5. Call Node.fireEvent().
  6. Add Handlers and/or Filters for EventTypes of interest.
  7. 为感兴趣的EventType添加处理程序和/或过滤器。

Some explanations:

If you want to create an event cascade, start with an "All" or "Any" type, that will be the root of all of the EventTypes:

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

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

This makes possible creating descendants of this type:

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

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

Then write the MyEvent class (which extends Event). The EventTypes should be typed to this event class (as is my example).

然后编写MyEvent类(扩展Event)。应该为此事件类键入EventTypes(就像我的示例一样)。

Now use (or in other words: fire) the event:

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

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

If you want to catch this event:

如果你想抓住这个事件:

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

#2


18  

Here is a (slightly over-complicated) sample application demonstrating some of the concepts that eckig outlines in his (excellent) answer.

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

The sample creates a visual field which is a tiled pane of reactor nodes. A custom lightning event is periodically sent to a random node, which will flash yellow when it receives the event. Filters and handlers are added to the parent field and their invocation reported to system.out so that you can see the event bubbling and capturing phases in action.

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

The code for the LightningEvent itself was mainly copied directly from the standard ActionEvent code in the JavaFX source, your event code could probably be a little simpler.

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

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

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();
        }

    }

}