我对发送到 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 插件的 Maven 目标
javafx:run
运行
<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
MouseEvent
javadoc 中进行了解释。
鼠标进入/退出处理
当鼠标进入某个节点时,该节点会得到 MOUSE_ENTERED 事件,当 当它离开时,它会收到 MOUSE_EXITED 事件。这些事件仅传递 到进入/退出的节点,并且似乎不经过 捕获/冒泡阶段。这是最常见的用例。
当需要捕获或冒泡时,有 MOUSE_ENTERED_TARGET/MOUSE_EXITED_TARGET 事件。这些事件去 通常通过捕获/冒泡阶段。这意味着家长可以 当鼠标进入任一目标时接收 MOUSE_ENTERED_TARGET 事件 父母本身或其一些孩子。为了区分 在这两种情况下,可以测试事件目标与 节点。
这两种类型紧密相连:MOUSE_ENTERED/MOUSE_EXITED 是 MOUSE_ENTERED_TARGET/MOUSE_EXITED_TARGET 的子类型。捕捉过程中 阶段,MOUSE_ENTERED_TARGET 被传递给父母。当 事件被传递到事件目标(实际发生事件的节点) 已输入),其类型切换为 MOUSE_ENTERED。那么类型是 在冒泡阶段切换回 MOUSE_ENTERED_TARGET。它是 还有一个事件只是切换类型,所以如果它被过滤或消耗, 它影响两个事件变体。 由于子类型关系, MOUSE_ENTERED_TARGET 事件处理程序将接收 MOUSE_ENTERED 事件达到目标。
我将最后一句加粗以示强调。
你写:
button.addEventHandler(MouseEvent.MOUSE_EXITED, event -> {
handleMouseEvent(desc, event);
});
button.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, event -> {
handleMouseEvent(desc, event);
});
根据文档,当鼠标退出按钮时,两个事件处理程序都将通过
MOUSE_EXITED
事件触发。 您正在处理各种类型的事件。 MOUSED_EXITED_TARGET 事件也是 MOUSE_EXITED 类型的事件,因此当您有两个事件的处理程序时,当鼠标退出目标时,两个事件都会被触发。
您可以从输出中看到,虽然只生成了一个事件(只是处理了多次),因为两个事件处理程序的输出都引用相同的事件对象:
Event : javafx.scene.input.MouseEvent@59a94fe0