如何正确设置自定义事件

How to Properly Set Up custom Event

好的 - 我已经为此奋斗了很久,阅读了一百万篇帖子、教程等。None 其中似乎直接解决了我在这里想做的事情。我有这个小代码示例来说明。

基本上,我希望能够 raise/fire 来自按钮的自定义事件并让标签响应该事件。

(请注意,除了我在这里尝试使用实际的事件对象之外,我没有兴趣寻找其他方法来做到这一点。我非常清楚如何使用更改侦听器等来做到这一点,但是我想学习如何这样做。)

这是代码,您可以看到它没有得到我正在寻找的结果。其中大部分来自一些示例(对我来说并没有真正起作用),我承认这里有些部分 "whoosh" 让我头疼。

如果有人可以帮助我让它工作,我可以稍后剖析它以确保我理解到底发生了什么。这是代码:

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

//-------------------------------------------------------------------
public class MyDemo extends Application {

    public static void main(String[] args) {
    launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

    // label that should receive the event and react to it
    MyLabel lblReceiver = new MyLabel("And I Should Receive & React");

    // button to firs the event
    Button btnSender = new Button("Initate Event");
    btnSender.setPrefWidth(200);
    btnSender.setOnAction(e -> {
        MyEvent.fireEvent(lblReceiver, e); // really confused what the first parameter here is supposed to be.
    });

    // set up stage and show it
    Stage stage = new Stage();
    VBox root = new VBox(btnSender, lblReceiver);
    root.setSpacing(10);
    root.setPadding(new Insets(10, 10, 10, 10));
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
    }

}
//-------------------------------------------------------------------

// Interface for objects that want to listen to my event
//-------------------------------------------------------------------
interface MyEventListener {
    void onMyEvent();
}
//-------------------------------------------------------------------

// My event definition itself
//-------------------------------------------------------------------
class MyEvent extends Event {

    public static final EventType<MyEvent> MY_EVENT = new EventType<>(ANY, "MY_EVENT");

    public MyEvent(EventType<? extends MyEvent> eventType) {
    super(eventType);
    }
}
//-------------------------------------------------------------------

//base/parent class for my label - this is what should receive/respond to event and
// where I'm sure i have problems - just don't know what.
//-------------------------------------------------------------------
class MyLabel extends Label implements MyEventListener {

    public MyLabel(String name) {
    this.setAlignment(Pos.CENTER);
    this.setText(name);
    this.setPrefWidth(200);
    }

    @Override // this is what I'm expecting to happen when i click the button
    public void onMyEvent() {
    this.setText("YAY! i got the event!");
    }

    private final ObjectProperty<EventHandler<? super MyEvent>> onMyEventProp
        = new SimpleObjectProperty<EventHandler<? super MyEvent>>(this, "onMyEvent") {
    @Override
    protected void invalidated() {
        setEventHandler(MyEvent.MY_EVENT, get());
    }
    };

    public final void setOnMyEvent(EventHandler<? super MyEvent> handler) {
    onMyEventProp.set(handler);
    }

    public final EventHandler<? super MyEvent> getOnMyEvent() {
    return onMyEventProp.get();
    }

    public final ObjectProperty<EventHandler<? super MyEvent>> onMyEventProperty() {
    return onMyEventProp;
    }

}
//-------------------------------------------------------------------

回答

除了两个问题外,您似乎已正确设置所有内容。

  1. 你永远不会添加一个 EventHandler 来监听你的事件。

    实现任意接口不会使您的对象对您的自定义事件做出反应。事件处理系统不知道你的接口,甚至不知道你已经实现了它。如果您希望在事件到达标签时调用 onMyEvent() 方法,则需要执行以下操作:

    public MyLabel() {
        //...
        addEventHandler(MyEvent.MY_EVENT, event -> onMyEvent());
    }
    

    注意我使用了 addEventHandler 以便 class 本身不依赖于 onMyEvent 属性。如果您依赖 属性,那么外部代码可能会通过替换 EventHandler.

    意外破坏您的代码

    这让我想知道 MyEventListener 接口是否真的有必要。你不能在 EventHandler 中做你需要的吗?

  2. 你从来没有真正触发过你的 MyEvent.

    您目前拥有:

    btnSender.setOnAction(e -> MyEvent.fireEvent(lblReceiver, e));
    

    这只是将 e(一个 ActionEvent)重新发射到 lblReceiverfireEvent 方法是由 javafx.event.Event 声明的 static 方法。在方法调用前加上 MyEvent 不会改变此处实际调用的方法。将其更改为以下内容:

    btnSender.setOnAction(e -> Event.fireEvent(lblReceiver, new MyEvent()));
    // or...
    btnSender.setOnAction(e -> lblReceiver.fireEvent(new MyEvent()));
    
    // The second option is a convenience method and simply forwards to Event.fireEvent
    

    为了在您的标签上实际触发您自己的事件实例 class。


事件调度的基础知识

JavaFX 中的事件处理有两个阶段:

  • 捕获阶段

    • 第一阶段。在这里,事件从其路径的起点向下传播到目标。在此过程中的每一步,都会调用适当的事件 filters。通过 Node.addEventFilter(EventType,EventHandler).
    • 等方法添加过滤器
  • 冒泡阶段

    • 第二阶段。在这里,事件从目标返回到路径的起​​点。在此过程中的每一步,都会调用适当的事件 处理程序 。处理程序是通过 Node.addEventHandler(EventType,EventHandler) 等方法和 Node.onKeyPressedButtonBase.onAction.
    • 等属性添加的

事件处理由以下部分组成:

  • javafx.event.Event(和子classes)

    • 传递的实际对象。携带与事件相关的信息(例如 MouseEvents 的光标位置)。

      Event 也带有 来源。但是,来源是动态的;它将始终是当前处理事件的 EventHandler 添加到的对象。因此,添加到 ButtonEventHandler 将以 Button 作为事件的源,但同一事件将以父级作为添加到父级的 EventHandler 的源.即使您两次使用相同的 EventHandler 实例,此行为也不会改变。

  • javafx.event.EventType

    • 细化了Event的class的含义。例如,带有 MOUSE_PRESSED 类型的 MouseEvent 意味着,毫不奇怪,鼠标被按下了。 EventType 有超类型;这为每种事件形成了一个跨 所有 类型的层次结构。为超类型注册的处理程序也将收到子类型的通知。

      您不能有两个或更多具有相同超类型和名称的 EventType。这就是为什么实例通常是相应事件 class.

    • public static final 字段
  • javafx.event.EventHandler

    • 处理其注册的任何事件。这是一个功能接口(可以是 lambda 表达式或方法引用的目标)。 application-level 是此接口的实现(即在触发按钮时执行某些操作)。
  • javafx.event.EventDispatcher

    • 负责将事件分派给适当的处理程序。每个能够成为事件目标的对象通常都有自己的调度程序。例如,每个 WindowSceneNode 实例都有自己的 EventDispatcher 实例(保存在 属性 中)。

      注意:在高度专业化的情况之外,您永远不必直接处理此接口。

  • javafx.event.EventDispatchChain

    • 表示事件在向目标发射时将采用的路径。此接口的实例用作 EventDispatcher 的堆栈。触发事件时,将根据事件的目标创建和配置新链。在场景图和目标是 Node 的情况下,堆栈由属于 WindowSceneEventDispatcher 组成,然后每个 Node 向下到(父到子)并包括目标。事件沿着这条链向下传播(捕获阶段),然后沿着这条链向上移动(冒泡阶段)。

      注意:您可能永远不需要直接使用此界面。

  • javafx.event.EventTarget

    • 事件的实际目标。 EventTarget 界面作为一种用于构建 EventDispatchChain 的方法。换句话说,目标决定了事件的路径。这是 Event.fireEvent(EventTarget,Event).

      中的第一个参数

      注意:如果您正在创建一个可以(显然)成为事件目标的对象(并且您的对象不是从 EventTarget).