如何正确设置自定义事件

时间:2019-05-26 16:47:26

标签: java events javafx event-handling custom-events

好的-我已经为此奋斗了,阅读了一百万篇文章,教程等。它们似乎都没有直接解决我在这里试图做的事情。我有这个小代码示例来说明。

基本上,我希望能够通过按钮引发/触发自定义事件,并让标签响应该事件。

((请注意,除了在实际的Event对象中尝试使用的方法之外,我对寻找其他方法没有兴趣。我非常清楚如何使用更改侦听器等进行此操作,但是我想要学习如何使用这种方法。)

这是代码,您可以看到这些代码并没有给我我想要的结果。其中大部分来自一些示例(对我而言实际上并不奏效),我承认这里有些部分令我头疼。

如果有人可以帮助我使其正常运行,我可以稍后对其进行解剖,以确保我确切地了解发生了什么。这是代码:

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 个答案:

答案 0 :(得分:2)

答案

除了两个问题外,您似乎一切设置正确。

  1. 您永远不会添加监听事件的EventHandler

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

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

    请注意,我使用了addEventHandler,以便类本身不依赖于onMyEvent属性。如果您依赖该属性,则外部代码可能会通过替换EventHandler意外地破坏您的代码。

    这使我想知道MyEventListener接口是否确实必要。您不能只是在EventHandler内部做您需要做的事情吗?

  2. 您实际上从未触发过MyEvent的实例。

    您当前拥有:

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

    这只是将e(一个ActionEvent)朝lblReceiver发射。 fireEvent方法是static声明的javafx.event.Event方法。用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
    

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


事件调度基础

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

  • 捕获阶段

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

    • 第二阶段。在此,事件从目标传播回路径的起点。在调用适当的事件 handlers 的每一步。通过诸如Node.addEventHandler(EventType,EventHandler)之类的方法以及诸如Node.onKeyPressedButtonBase.onAction之类的属性来添加处理程序。

事件处理包含以下组件:

  • javafx.event.Event(和子类)

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

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

  • javafx.event.EventType

    • 定义EventHandler类的含义。例如,Event类型的MouseEvent意味着鼠标被按下就不足为奇了。 MOUSE_PRESSED具有超类型;这为每种事件形成了 all 类型的层次结构。注册为超类型的处理程序还将收到子类型的通知。

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

  • javafx.event.EventHandler

    • 处理其注册的任何事件。这是一个功能接口(可以是lambda表达式或方法引用的目标)。该接口的实现完成了应用程序级的工作(即,在触发按钮时执行某些操作)。
  • javafx.event.EventDispatcher

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

      注意:在高度专业化的情况下,您将不必直接处理此界面。

  • javafx.event.EventDispatchChain

    • 表示事件在目标上触发时将采用的路径。该接口的实例充当EventDispatcher的堆栈。触发事件时,将基于事件的目标创建并配置新链。在场景图且目标是EventDispatcher的情况下,堆栈由属于Node的{​​{1}},EventDispatcher和每个{{ 1}}到(父母对孩子)并包括目标。事件沿着该链(捕获阶段)传播,然后备份到该链(冒泡阶段)。

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

  • javafx.event.EventTarget

    • 事件的实际目标。 Window接口具有一种用于构建Scene的方法。换句话说,目标确定事件的路径。这是Node中的第一个参数。

      注意:仅当您要创建可以(显然)成为事件目标的对象(并且该对象并不从EventTarget的实现扩展而来)时,才需要使用此接口。