我对根据鼠标指针发生的情况发送到 JavaFX按钮控件的事件进行了一些实验,并从中生成了一个分层状态机图(近似,因为我的编辑器没有任何语义概念):
这一切都非常简单,只需从起始状态跟踪行为并查看会发生什么。这里有一个微妙之处,因为按钮控件内的标签也起着作用,它会生成MOUSE_ENTERED_TARGET
事件MOUSE_EXITED_TARGET
(我向 ChatGPT 询问了这个问题,它打印了一个非常漂亮的答案,但答案是错的😂)
我唯一想知道的一点是——为什么我会持续收到两件MOUSE_ENTERED
或两个MOUSE_EXITED
事件,一个带有consumed = false
,一个带有consumed = true
?
示例代码
以下是(接近最小的)示例。它只是创建了一个按钮,我们可以手动点击它来查看生成了哪些事件:
文件com.example.stack_overflow.Main.java
package com.example.stack_overflow;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.Objects;
class PaneBuilder {
private final StackPane stackPane;
private static void log(String text) {
System.out.println(text);
}
public PaneBuilder() {
stackPane = buildCenteringStackPaneAroundButton(buildButton());
}
private static Button buildButton() {
final var button = new Button("Click Me!");
button.setMinWidth(120); // I think these are "120pt", why is there no "unit"?
final var desc = "Button";
button.addEventHandler(ActionEvent.ACTION, event -> {
handleActionEvent(desc, event);
});
button.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
handleMouseEvent(desc, event);
});
button.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {
handleMouseEvent(desc, event);
});
button.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
handleMouseEvent(desc, event);
});
button.addEventHandler(MouseEvent.MOUSE_ENTERED, event -> {
handleMouseEvent(desc, event);
});
button.addEventHandler(MouseEvent.MOUSE_EXITED, event -> {
handleMouseEvent(desc, event);
});
button.addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, event -> {
handleMouseEvent(desc, event);
});
button.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, event -> {
handleMouseEvent(desc, event);
});
button.armedProperty().addListener((obs, oldVal, newVal) -> log(desc + ": armed status changes: " + oldVal + " -> " + newVal));
return button;
}
public Pane getPane() {
return stackPane;
}
private static StackPane buildCenteringStackPaneAroundButton(Button button) {
final var stackPane = new StackPane();
stackPane.setAlignment(Pos.CENTER);
final var hbox = buildHBoxAroundVBoxAroundButton(button);
final var insets = new Insets(20, 20, 20, 20); // I guess those are "20points"
StackPane.setMargin(hbox, insets);
stackPane.getChildren().add(hbox);
return stackPane;
}
private static HBox buildHBoxAroundVBoxAroundButton(Button button) {
final var hbox = new HBox();
hbox.setAlignment(Pos.CENTER);
hbox.getChildren().add(buildVBoxAroundButton(button));
return hbox;
}
private static VBox buildVBoxAroundButton(Button button) {
final var vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
vbox.getChildren().add(button);
return vbox;
}
private static void appendEventDesc(StringBuilder buf, Event event) {
buf.append("\n Type : " + event.getEventType());
buf.append("\n Class : " + event.getClass().getName());
buf.append("\n Consumed : " + event.isConsumed());
buf.append("\n Source class : " + event.getSource().getClass().getName());
buf.append("\n Event : " + Objects.toIdentityString(event));
}
public static void handleMouseEvent(String desc, MouseEvent event) {
final var buf = new StringBuilder("Mouse event in " + desc);
appendEventDesc(buf, event);
log(buf.toString());
}
public static void handleActionEvent(String desc, ActionEvent event) {
final var buf = new StringBuilder("Action event in " + desc);
appendEventDesc(buf, event);
log(buf.toString());
}
}
public class Main extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("Button Example");
stage.setScene(new Scene(new PaneBuilder().getPane()));
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
以及构建和运行上述程序的 POM。该程序必须使用JavaFX Maven 插件javafx:run
的Maven 目标运行
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>stack_overflow</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- RUNNING VIA MAVEN + JAVAFX PLUGIN IN IDE: -->
<!-- 'Main Menu' > 'Run' > 'Run Maven Goal' > 'Plugins' > 'JavaFx Maven Plugin' > 'javafx:run' -->
<!-- This will invoke the goal "javafx:run" of the "javafx-maven-plugin". -->
<!-- With the 'Run New Maven Goal' menu entry, you can define that goal, and it will appear in the -->
<!-- context menu as 'right-mouse-button menu' > 'run maven' > 'javafx:run' -->
<!-- RUNNING VIA MAVEN IN COMMAND LINE w/o leaving the IDE (no need for OpenJDX SDK): -->
<!-- Right-click on the project window and select "Open Terminal at the current Maven module path". -->
<!-- Enter the command "mvn javafx:run" or for debugging output "mvn -X javafx:run". -->
<!-- This will invoke the goal "javafx:run" of the "javafx-maven-plugin". -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- This is the latest OpenJavaFX version on 2024-09-26 (version of 2024-09-16) -->
<!-- https://mvnrepository.com/artifact/org.openjfx/javafx-controls -->
<javafx.version>23</javafx.version>
<javafx.plugin.version>0.0.8</javafx.plugin.version>
<compiler.plugin.version>3.13.0</compiler.plugin.version>
<exec.plugin.version>3.4.1</exec.plugin.version>
<dependency.plugin.version>3.8.0</dependency.plugin.version>
<main.class>com.example.stack_overflow.Main</main.class>
<java.compiler.source.version>21</java.compiler.source.version>
<java.compiler.target.version>21</java.compiler.target.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.openjfx/javafx-controls -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjfx/javafx-fxml -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Standard Java Compiler Plugin -->
<!-- https://maven.apache.org/plugins/maven-compiler-plugin/ -->
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-compiler-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler.plugin.version}</version>
<configuration>
<source>${java.compiler.source.version}</source>
<target>${java.compiler.target.version}</target>
</configuration>
</plugin>
<!-- Special Plugin to run JavaFX programs -->
<!-- https://github.com/openjfx/javafx-maven-plugin -->
<!-- https://mvnrepository.com/artifact/org.openjfx/javafx-maven-plugin -->
<!-- If you run the plugin's goal on the command line with the '-X' option like so: -->
<!-- mvn -X javafx:run -->
<!-- you will see the command line the plugin builds, which looks as follows, with ++ replaced by double dash -->
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>${javafx.plugin.version}</version>
<configuration>
<mainClass>${main.class}</mainClass>
<options>
<option>-ea</option>
</options>
</configuration>
</plugin>
</plugins>
</build>
</project>
运行该程序会出现以下窗口:
如果我们现在用鼠标指针沿着指示的红色路径移动,然后单击鼠标并再次将鼠标指针移出按钮:
我们得到了属性值armed
和按钮发出的事件的以下变化。请注意双引号MOUSE_ENTERED
= consumed
,false
然后是true
。
进入按钮控件:
Mouse event in Button
Type : MOUSE_ENTERED
Class : javafx.scene.input.MouseEvent
Consumed : false
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@2ebee20a
Mouse event in Button
Type : MOUSE_ENTERED
Class : javafx.scene.input.MouseEvent
Consumed : true
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@2ebee20a
输入按钮控件的标签:
Mouse event in Button
Type : MOUSE_ENTERED_TARGET
Class : javafx.scene.input.MouseEvent
Consumed : false
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@5d41df9f
单击,注意状态的变化armed
。
Mouse event in Button
Type : MOUSE_PRESSED
Class : javafx.scene.input.MouseEvent
Consumed : false
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@3df998ed
Button: armed status changes: false -> true
Mouse event in Button
Type : MOUSE_RELEASED
Class : javafx.scene.input.MouseEvent
Consumed : false
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@241a11d5
Action event in Button
Type : ACTION
Class : javafx.event.ActionEvent
Consumed : false
Source class : javafx.scene.control.Button
Event : javafx.event.ActionEvent@3d8fce01
Button: armed status changes: true -> false
Mouse event in Button
Type : MOUSE_CLICKED
Class : javafx.scene.input.MouseEvent
Consumed : false
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@505036ef
退出按钮控件的标签:
Mouse event in Button
Type : MOUSE_EXITED_TARGET
Class : javafx.scene.input.MouseEvent
Consumed : false
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@2d496725
退出按钮控制:
Mouse event in Button
Type : MOUSE_EXITED
Class : javafx.scene.input.MouseEvent
Consumed : false
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@59a94fe0
Mouse event in Button
Type : MOUSE_EXITED
Class : javafx.scene.input.MouseEvent
Consumed : true
Source class : javafx.scene.control.Button
Event : javafx.scene.input.MouseEvent@59a94fe0
MOUSE_EXITED
根据文档,当鼠标退出按钮时,MOUSE_EXITED 和 MOUSED_EXITED_TARGET 事件处理程序都会触发事件。您正在处理这两种类型的事件。MOUSED_EXITED_TARGET 事件也是 MOUSE_EXITED 类型的事件,因此当您同时为这两种事件设置处理程序时,当鼠标离开目标时,这两种事件都会被触发。
你写道:
但是您可以从输出中看到,只生成了一个事件(只是处理了多次),因为两个事件处理程序的输出都引用了同一个事件对象:
解释
MouseEvent
javadoc中对此进行了解释。抱歉复制了这么大一段,但是它有点复杂,我自己无法用更简短的方式更好地解释它。
我把最后一句话用粗体表示以强调。
附录
Stack Overflow 不允许“对某人的帖子发表评论”,因此我将其作为附加答案:
经过修正的不完全是 UML 的状态图,允许跟踪按钮发出的事件。它需要一些修改才能成为真正的 UML,嵌套状态甚至可能会改进它。但目前这样就够了。
对于仅包含标签的按钮控件: