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 {@Overridepublic 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>() {@Overridepublic void handle(ActionEvent event) {Platform.exit();}});root.setOnMousePressed(new EventHandler<MouseEvent>() {@Overridepublic 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>() {@Overridepublic void handle(ActionEvent event) {Platform.exit();}});
使用setOnAction()方法,我们为ActionEvent设置了一个事件处理器。 EventHandler的handle()方法以Platform.exit()方法退出应用。
root.setOnMousePressed(new EventHandler<MouseEvent>() {@Overridepublic 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 {@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic 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 {@Overridepublic 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> {@Overridepublic 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> {@Overridepublic 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;@Overridepublic 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> {@Overridepublic 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> {@Overridepublic 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;@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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;@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic 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 事件。
