如何正确设置自定义事件
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;
}
}
//-------------------------------------------------------------------
回答
除了两个问题外,您似乎已正确设置所有内容。
你永远不会添加一个 EventHandler
来监听你的事件。
实现任意接口不会使您的对象对您的自定义事件做出反应。事件处理系统不知道你的接口,甚至不知道你已经实现了它。如果您希望在事件到达标签时调用 onMyEvent()
方法,则需要执行以下操作:
public MyLabel() {
//...
addEventHandler(MyEvent.MY_EVENT, event -> onMyEvent());
}
注意我使用了 addEventHandler
以便 class 本身不依赖于 onMyEvent
属性。如果您依赖 属性,那么外部代码可能会通过替换 EventHandler
.
意外破坏您的代码
这让我想知道 MyEventListener
接口是否真的有必要。你不能在 EventHandler
中做你需要的吗?
你从来没有真正触发过你的 MyEvent
.
您目前拥有:
btnSender.setOnAction(e -> MyEvent.fireEvent(lblReceiver, e));
这只是将 e
(一个 ActionEvent
)重新发射到 lblReceiver
。 fireEvent
方法是由 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.onKeyPressed
和 ButtonBase.onAction
. 等属性添加的
事件处理由以下部分组成:
javafx.event.Event
(和子classes)
传递的实际对象。携带与事件相关的信息(例如 MouseEvent
s 的光标位置)。
Event
也带有 来源。但是,来源是动态的;它将始终是当前处理事件的 EventHandler
添加到的对象。因此,添加到 Button
的 EventHandler
将以 Button
作为事件的源,但同一事件将以父级作为添加到父级的 EventHandler
的源.即使您两次使用相同的 EventHandler
实例,此行为也不会改变。
-
细化了Event
的class的含义。例如,带有 MOUSE_PRESSED
类型的 MouseEvent
意味着,毫不奇怪,鼠标被按下了。 EventType
有超类型;这为每种事件形成了一个跨 所有 类型的层次结构。为超类型注册的处理程序也将收到子类型的通知。
您不能有两个或更多具有相同超类型和名称的 EventType
。这就是为什么实例通常是相应事件 class.
的 public static final
字段
-
- 处理其注册的任何事件。这是一个功能接口(可以是 lambda 表达式或方法引用的目标)。 application-level 是此接口的实现(即在触发按钮时执行某些操作)。
-
负责将事件分派给适当的处理程序。每个能够成为事件目标的对象通常都有自己的调度程序。例如,每个 Window
、Scene
和 Node
实例都有自己的 EventDispatcher
实例(保存在 属性 中)。
注意:在高度专业化的情况之外,您永远不必直接处理此接口。
javafx.event.EventDispatchChain
表示事件在向目标发射时将采用的路径。此接口的实例用作 EventDispatcher
的堆栈。触发事件时,将根据事件的目标创建和配置新链。在场景图和目标是 Node
的情况下,堆栈由属于 Window
、Scene
的 EventDispatcher
组成,然后每个 Node
向下到(父到子)并包括目标。事件沿着这条链向下传播(捕获阶段),然后沿着这条链向上移动(冒泡阶段)。
注意:您可能永远不需要直接使用此界面。
-
事件的实际目标。 EventTarget
界面作为一种用于构建 EventDispatchChain
的方法。换句话说,目标决定了事件的路径。这是 Event.fireEvent(EventTarget,Event)
.
中的第一个参数
注意:如果您正在创建一个可以(显然)成为事件目标的对象(并且您的对象不是从 EventTarget
).
好的 - 我已经为此奋斗了很久,阅读了一百万篇帖子、教程等。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;
}
}
//-------------------------------------------------------------------
回答
除了两个问题外,您似乎已正确设置所有内容。
你永远不会添加一个
EventHandler
来监听你的事件。实现任意接口不会使您的对象对您的自定义事件做出反应。事件处理系统不知道你的接口,甚至不知道你已经实现了它。如果您希望在事件到达标签时调用
onMyEvent()
方法,则需要执行以下操作:public MyLabel() { //... addEventHandler(MyEvent.MY_EVENT, event -> onMyEvent()); }
注意我使用了
意外破坏您的代码addEventHandler
以便 class 本身不依赖于onMyEvent
属性。如果您依赖 属性,那么外部代码可能会通过替换EventHandler
.这让我想知道
MyEventListener
接口是否真的有必要。你不能在EventHandler
中做你需要的吗?你从来没有真正触发过你的
MyEvent
.您目前拥有:
btnSender.setOnAction(e -> MyEvent.fireEvent(lblReceiver, e));
这只是将
e
(一个ActionEvent
)重新发射到lblReceiver
。fireEvent
方法是由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)
. 等方法添加过滤器
- 第一阶段。在这里,事件从其路径的起点向下传播到目标。在此过程中的每一步,都会调用适当的事件 filters。通过
冒泡阶段
- 第二阶段。在这里,事件从目标返回到路径的起点。在此过程中的每一步,都会调用适当的事件 处理程序 。处理程序是通过
Node.addEventHandler(EventType,EventHandler)
等方法和Node.onKeyPressed
和ButtonBase.onAction
. 等属性添加的
- 第二阶段。在这里,事件从目标返回到路径的起点。在此过程中的每一步,都会调用适当的事件 处理程序 。处理程序是通过
事件处理由以下部分组成:
javafx.event.Event
(和子classes)传递的实际对象。携带与事件相关的信息(例如
MouseEvent
s 的光标位置)。Event
也带有 来源。但是,来源是动态的;它将始终是当前处理事件的EventHandler
添加到的对象。因此,添加到Button
的EventHandler
将以Button
作为事件的源,但同一事件将以父级作为添加到父级的EventHandler
的源.即使您两次使用相同的EventHandler
实例,此行为也不会改变。
-
细化了
Event
的class的含义。例如,带有MOUSE_PRESSED
类型的MouseEvent
意味着,毫不奇怪,鼠标被按下了。EventType
有超类型;这为每种事件形成了一个跨 所有 类型的层次结构。为超类型注册的处理程序也将收到子类型的通知。您不能有两个或更多具有相同超类型和名称的
EventType
。这就是为什么实例通常是相应事件 class. 的
public static final
字段 -
- 处理其注册的任何事件。这是一个功能接口(可以是 lambda 表达式或方法引用的目标)。 application-level 是此接口的实现(即在触发按钮时执行某些操作)。
-
负责将事件分派给适当的处理程序。每个能够成为事件目标的对象通常都有自己的调度程序。例如,每个
Window
、Scene
和Node
实例都有自己的EventDispatcher
实例(保存在 属性 中)。注意:在高度专业化的情况之外,您永远不必直接处理此接口。
javafx.event.EventDispatchChain
表示事件在向目标发射时将采用的路径。此接口的实例用作
EventDispatcher
的堆栈。触发事件时,将根据事件的目标创建和配置新链。在场景图和目标是Node
的情况下,堆栈由属于Window
、Scene
的EventDispatcher
组成,然后每个Node
向下到(父到子)并包括目标。事件沿着这条链向下传播(捕获阶段),然后沿着这条链向上移动(冒泡阶段)。注意:您可能永远不需要直接使用此界面。
-
事件的实际目标。
中的第一个参数EventTarget
界面作为一种用于构建EventDispatchChain
的方法。换句话说,目标决定了事件的路径。这是Event.fireEvent(EventTarget,Event)
.注意:如果您正在创建一个可以(显然)成为事件目标的对象(并且您的对象不是从
EventTarget
).