JavaFX 事件
GUI 应用是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件是由用户(单击鼠标),应用(计时器)或系统(时钟)生成的。
事件是有关更改的通知。 它将状态更改封装在事件源中。 应用中已注册的事件过滤器和事件处理器将接收事件并提供响应。
JavaFX 中的每个事件都具有三个属性:
- 事件来源
- 事件目标
- 事件类型
事件源是状态更改的对象; 它产生事件。事件目标是事件的目的地。事件类型为相同Event
类的事件提供了额外的分类。
事件源对象将处理事件的任务委托给事件处理器。 当事件发生时,事件源创建一个事件对象,并将其发送到每个注册的处理器。
事件处理器
EventHandler
处理特定类或类型的事件。 事件处理器设置为事件源。 它具有handle()
方法,在该方法中,我们将为响应生成的事件而调用的代码放入其中。
EventHandlerEx.java
package com.zetcode;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
* ZetCode JavaFX tutorial
*
* This program uses two EventHandlers for
* two different Events.
*
* Author: Jan Bodnar
* Website: zetcode.com
* Last modified: June 2015
*/
public class EventHandlerEx extends Application {
@Override
public void start(Stage stage) {
initUI(stage);
}
private void initUI(Stage stage) {
HBox root = new HBox();
ContextMenu conMenu = new ContextMenu();
MenuItem noopMi = new MenuItem("No op");
MenuItem exitMi = new MenuItem("Exit");
conMenu.getItems().addAll(noopMi, exitMi);
exitMi.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
Platform.exit();
}
});
root.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
if (event.isSecondaryButtonDown()) {
conMenu.show(root, event.getScreenX(),
event.getScreenY());
}
}
});
Scene scene = new Scene(root, 300, 250);
stage.setTitle("EventHandler");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
该示例将两个EventHandlers
用于两个不同的Events
。
ContextMenu conMenu = new ContextMenu();
ContextMenu
是一个包含菜单项列表的弹出控件。
MenuItem noop = new MenuItem("No op");
MenuItem exit = new MenuItem("Exit");
conMenu.getItems().addAll(noop, exit);
将创建两个MenuItems
并将其添加到上下文菜单。
exitMi.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
Platform.exit();
}
});
使用setOnAction()
方法,我们为ActionEvent
设置了一个事件处理器。 EventHandler
的handle()
方法以Platform.exit()
方法退出应用。
root.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
if (event.isSecondaryButtonDown()) {
conMenu.show(root, event.getScreenX(),
event.getScreenY());
}
}
});
使用setOnMousePressed()
方法,我们为MouseEvent
设置了一个事件处理器。 当我们单击第二个鼠标按钮(通常是右按钮)时,上下文菜单将显示在屏幕上。 它显示在鼠标单击的 x 和 y 坐标下方。
事件属性
以下程序探讨了MouseEvent
的属性。 这是由于用户与鼠标交互而发生的事件。
EventSourceEx.java
package com.zetcode;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
* ZetCode JavaFX tutorial
*
* This program explores the properties of
* an event.
*
* Author: Jan Bodnar
* Website: zetcode.com
* Last modified: June 2015
*/
public class EventSourceEx extends Application {
@Override
public void start(Stage stage) {
initUI(stage);
}
private void initUI(Stage stage) {
Pane root = new Pane();
Rectangle rect = new Rectangle(30, 30, 80, 80);
rect.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
System.out.println(e.getSource());
System.out.println(e.getTarget());
System.out.println(e.getEventType());
System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());
}
});
root.getChildren().addAll(rect);
Scene scene = new Scene(root, 300, 250);
stage.setTitle("Event properties");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
在示例中,我们有一个矩形。 我们将事件处理器添加到鼠标单击的事件类型。
rect.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
...
}
});
setOnMouseClicked()
将事件处理器添加到鼠标单击的事件类型。 处理器是一个匿名内部类。 当在矩形上检测到鼠标单击时,将调用其handle()
方法。
System.out.println(e.getSource());
System.out.println(e.getTarget());
System.out.println(e.getEventType());
这三个是通用属性,可用于所有事件。 getSource()
方法返回最初发生事件的对象。 getTarget()
方法返回此事件的事件目标。 在我们的例子中,事件源和事件目标是相同的-矩形。 getEventType()
方法返回MouseEvent
的事件类型。 在我们的情况下,它返回MOUSE_CLICKED
值。
System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());
这四个属性特定于此事件。 我们打印相对于场景和屏幕的鼠标单击的 x 和 y 坐标。
Lambda 表达式
从 JDK 8 开始,可以使用 lambda 表达式替换匿名内部类。
rect.setOnMouseClicked((MouseEvent e) -> {
System.out.println(e.getSource());
System.out.println(e.getTarget());
System.out.println(e.getEventType());
System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());
});
这是使用 lambda 表达式重写的上一个示例中的事件处理代码。
通用处理器
在下一个示例中,我们创建一个监听所有类型事件的通用事件处理器。
GenericHandlerEx.java
package com.zetcode;
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* ZetCode JavaFX tutorial
*
* This program adds a generic event
* handler to a button control.
*
* Author: Jan Bodnar
* Website: zetcode.com
* Last modified: June 2015
*/
public class GenericHandlerEx extends Application {
@Override
public void start(Stage stage) {
initUI(stage);
}
private void initUI(Stage stage) {
StackPane root = new StackPane();
Button btn = new Button("Button");
btn.addEventHandler(EventType.ROOT, new GenericHandler());
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
stage.setTitle("Generic handler");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private class GenericHandler implements EventHandler<Event> {
@Override
public void handle(Event event) {
System.out.println(event.getEventType());
}
}
}
本示例具有一个按钮控件。 通用处理器已插入按钮。
Button btn = new Button("Button");
btn.addEventHandler(EventType.ROOT, new GenericHandler());
addEventHandler()
方法将事件处理器注册到指定事件类型的按钮节点。 EventType.ROOT
代表所有事件类型。
private class GenericHandler implements EventHandler<Event> {
@Override
public void handle(Event event) {
System.out.println(event.getEventType());
}
}
处理器使用其handle()
方法将事件类型打印到控制台。
多种来源
可以将单个事件处理器添加到多个源。 可以使用getSource()
方法确定事件的来源。
MultipleSourcesEx.java
package com.zetcode;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* ZetCode JavaFX tutorial
*
* This program plugs an EventHandler to multiple
* controls.
*
* Author: Jan Bodnar
* Website: zetcode.com
* Last modified: June 2015
*/
public class MultipleSourcesEx extends Application {
private Label lbl;
@Override
public void start(Stage stage) {
initUI(stage);
}
private void initUI(Stage stage) {
AnchorPane root = new AnchorPane();
VBox vbox = new VBox(5);
Button btn1 = new Button("Close");
Button btn2 = new Button("Open");
Button btn3 = new Button("Find");
Button btn4 = new Button("Save");
MyButtonHandler mbh = new MyButtonHandler();
btn1.setOnAction(mbh);
btn2.setOnAction(mbh);
btn3.setOnAction(mbh);
btn4.setOnAction(mbh);
vbox.getChildren().addAll(btn1, btn2, btn3, btn4);
lbl = new Label("Ready");
AnchorPane.setTopAnchor(vbox, 10d);
AnchorPane.setLeftAnchor(vbox, 10d);
AnchorPane.setBottomAnchor(lbl, 10d);
AnchorPane.setLeftAnchor(lbl, 10d);
root.getChildren().addAll(vbox, lbl);
Scene scene = new Scene(root, 350, 200);
stage.setTitle("Multiple sources");
stage.setScene(scene);
stage.show();
}
private class MyButtonHandler implements EventHandler<ActionEvent> {
@Override
public void handle(ActionEvent event) {
Button btn = (Button) event.getSource();
lbl.setText(String.format("Button %s fired", btn.getText()));
}
}
public static void main(String[] args) {
launch(args);
}
}
该示例有四个按钮和一个标签。 一个事件处理器将添加到所有四个按钮。 触发按钮的名称显示在标签中。
Button btn1 = new Button("Close");
Button btn2 = new Button("Open");
Button btn3 = new Button("Find");
Button btn4 = new Button("Save");
这四个按钮将共享一个事件处理器。
MyButtonHandler mbh = new MyButtonHandler();
创建一个MyButtonHandler
的实例。 它作为内部命名类实现。
btn1.setOnAction(mbh);
btn2.setOnAction(mbh);
btn3.setOnAction(mbh);
btn4.setOnAction(mbh);
使用setOnAction()
方法将处理器添加到四个不同的按钮。
private class MyButtonHandler implements EventHandler<ActionEvent> {
@Override
public void handle(ActionEvent event) {
Button btn = (Button) event.getSource();
lbl.setText(String.format("Button %s fired", btn.getText()));
}
}
在MyButtonHandler
的handle()
方法内部,我们确定事件的来源并使用来源的文本标签构建消息。 该消息通过setText()
方法设置为标签控件。
图:多个来源
java.util.Timer
java.util.Timer
计划任务以供将来在后台线程中执行。 TimerTask
是可以计划为一次性执行或由计时器重复执行的任务。
TimerEx.java
package com.zetcode;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Spinner;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
* ZetCode JavaFX tutorial
*
* This program uses a java.util.Timer to
* schedule a task.
*
* Author: Jan Bodnar
* Website: zetcode.com
* Last modified: June 2015
*/
public class TimerEx extends Application {
int delay = 0;
@Override
public void start(Stage stage) {
initUI(stage);
}
private void initUI(Stage stage) {
HBox root = new HBox(10);
root.setPadding(new Insets(10));
Timer timer = new java.util.Timer();
Spinner spinner = new Spinner(1, 60, 5);
spinner.setPrefWidth(80);
Button btn = new Button("Show message");
btn.setOnAction(event -> {
delay = (int) spinner.getValue();
timer.schedule(new MyTimerTask(), delay*1000);
});
root.getChildren().addAll(btn, spinner);
stage.setOnCloseRequest(event -> {
timer.cancel();
});
Scene scene = new Scene(root);
stage.setTitle("Timer");
stage.setScene(scene);
stage.show();
}
private class MyTimerTask extends TimerTask {
@Override
public void run() {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Information dialog");
alert.setHeaderText("Time elapsed information");
String contxt;
if (delay == 1) {
contxt = "1 second has elapsed";
} else {
contxt = String.format("%d seconds have elapsed",
delay);
}
alert.setContentText(contxt);
alert.showAndWait();
});
}
}
public static void main(String[] args) {
launch(args);
}
}
该示例有两个控件:一个按钮和一个微调器。 该按钮将启动计时器,延迟后将显示一个消息对话框。 延迟由微调控件选择。
Timer timer = new java.util.Timer();
创建java.util.Timer
的实例。
Spinner spinner = new Spinner(1, 60, 5);
Spinner
控件用于选择延迟量。 它的参数是最小值,最大值和当前值。 该值以毫秒为单位。
btn.setOnAction(event -> {
delay = (int) spinner.getValue();
timer.schedule(new MyTimerTask(), delay*1000);
});
在按钮的事件处理器中,我们使用getValue()
方法获取微调框的当前值,并使用计时器的schedule()
方法安排任务。
stage.setOnCloseRequest(event -> {
timer.cancel();
});
当使用计时器的cancel()
方法终止应用时,我们将取消计时器。
private class MyTimerTask extends TimerTask {
@Override
public void run() {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Information dialog");
alert.setHeaderText("Time elapsed information");
String contxt;
if (delay == 1) {
contxt = "1 second has elapsed";
} else {
contxt = String.format("%d seconds have elapsed",
delay);
}
alert.setContentText(contxt);
alert.showAndWait();
});
}
}
runLater()
方法在 JavaFX 应用线程上执行任务。 我们显示一个消息对话框,通知您经过的时间。
图:经过的时间
移动窗口
以下示例显示了应用窗口在屏幕上的位置。
MovingWindowEx.java
package com.zetcode;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* ZetCode JavaFX tutorial
*
* This program shows the screen coordinates
* of the application window in two labels.
*
* Author: Jan Bodnar
* Website: zetcode.com
* Last modified: June 2015
*/
public class MovingWindowEx extends Application {
int x = 0;
int y = 0;
Label lblx;
Label lbly;
@Override
public void start(Stage stage) {
initUI(stage);
}
private void initUI(Stage stage) {
VBox root = new VBox(10);
root.setPadding(new Insets(10));
String txt1 = String.format("x: %d", x);
lblx = new Label(txt1);
String txt2 = String.format("y: %d", y);
lbly = new Label(txt2);
root.getChildren().addAll(lblx, lbly);
stage.xProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
doChange(newValue);
}
private void doChange(Number newValue) {
x = newValue.intValue();
updateXLabel();
}
});
stage.yProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
doChange(newValue);
}
private void doChange(Number newValue) {
y = newValue.intValue();
updateYLabel();
}
});
Scene scene = new Scene(root, 300, 250);
stage.setTitle("Moving window");
stage.setScene(scene);
stage.show();
}
private void updateXLabel() {
String txt = String.format("x: %d", x);
lblx.setText(txt);
}
private void updateYLabel() {
String txt = String.format("y: %d", y);
lbly.setText(txt);
}
public static void main(String[] args) {
launch(args);
}
}
该示例显示了两个标签控件中的当前窗口坐标。 为了获得窗口位置,我们监听舞台的xProperty
和yProperty
的变化。
String txt1 = String.format("x: %d", x);
lblx = new Label(txt1);
String txt2 = String.format("y: %d", y);
lbly = new Label(txt2);
这两个标签显示了应用窗口左上角的 x 和 y 坐标。
stage.xProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
doChange(newValue);
}
private void doChange(Number newValue) {
x = newValue.intValue();
updateXLabel();
}
});
xProperty
将舞台的水平位置存储在屏幕上。 我们添加一个ChangeListener
来监听属性的更改。 每次修改属性时,我们都会检索新值并更新标签。
图:移动窗口
JavaFX 教程的这一部分专门讨论 JavaFX 事件。