原文: http://zetcode.com/gui/javafx/events/

GUI 应用是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件是由用户(单击鼠标),应用(计时器)或系统(时钟)生成的。

事件是有关更改的通知。 它将状态更改封装在事件源中。 应用中已注册的事件过滤器和事件处理器将接收事件并提供响应。

JavaFX 中的每个事件都具有三个属性:

  • 事件来源
  • 事件目标
  • 事件类型

事件源是状态更改的对象; 它产生事件。事件目标是事件的目的地。事件类型为相同Event类的事件提供了额外的分类。

事件源对象将处理事件的任务委托给事件处理器。 当事件发生时,事件源创建一个事件对象,并将其发送到每个注册的处理器。

事件处理器

EventHandler处理特定类或类型的事件。 事件处理器设置为事件源。 它具有handle()方法,在该方法中,我们将为响应生成的事件而调用的代码放入其中。

EventHandlerEx.java

  1. package com.zetcode;
  2. import javafx.application.Application;
  3. import javafx.application.Platform;
  4. import javafx.event.ActionEvent;
  5. import javafx.event.EventHandler;
  6. import javafx.scene.Scene;
  7. import javafx.scene.control.ContextMenu;
  8. import javafx.scene.control.MenuItem;
  9. import javafx.scene.input.MouseEvent;
  10. import javafx.scene.layout.HBox;
  11. import javafx.stage.Stage;
  12. /**
  13. * ZetCode JavaFX tutorial
  14. *
  15. * This program uses two EventHandlers for
  16. * two different Events.
  17. *
  18. * Author: Jan Bodnar
  19. * Website: zetcode.com
  20. * Last modified: June 2015
  21. */
  22. public class EventHandlerEx extends Application {
  23. @Override
  24. public void start(Stage stage) {
  25. initUI(stage);
  26. }
  27. private void initUI(Stage stage) {
  28. HBox root = new HBox();
  29. ContextMenu conMenu = new ContextMenu();
  30. MenuItem noopMi = new MenuItem("No op");
  31. MenuItem exitMi = new MenuItem("Exit");
  32. conMenu.getItems().addAll(noopMi, exitMi);
  33. exitMi.setOnAction(new EventHandler<ActionEvent>() {
  34. @Override
  35. public void handle(ActionEvent event) {
  36. Platform.exit();
  37. }
  38. });
  39. root.setOnMousePressed(new EventHandler<MouseEvent>() {
  40. @Override
  41. public void handle(MouseEvent event) {
  42. if (event.isSecondaryButtonDown()) {
  43. conMenu.show(root, event.getScreenX(),
  44. event.getScreenY());
  45. }
  46. }
  47. });
  48. Scene scene = new Scene(root, 300, 250);
  49. stage.setTitle("EventHandler");
  50. stage.setScene(scene);
  51. stage.show();
  52. }
  53. public static void main(String[] args) {
  54. launch(args);
  55. }
  56. }

该示例将两个EventHandlers用于两个不同的Events

  1. ContextMenu conMenu = new ContextMenu();

ContextMenu是一个包含菜单项列表的弹出控件。

  1. MenuItem noop = new MenuItem("No op");
  2. MenuItem exit = new MenuItem("Exit");
  3. conMenu.getItems().addAll(noop, exit);

将创建两个MenuItems并将其添加到上下文菜单。

  1. exitMi.setOnAction(new EventHandler<ActionEvent>() {
  2. @Override
  3. public void handle(ActionEvent event) {
  4. Platform.exit();
  5. }
  6. });

使用setOnAction()方法,我们为ActionEvent设置了一个事件处理器。 EventHandlerhandle()方法以Platform.exit()方法退出应用。

  1. root.setOnMousePressed(new EventHandler<MouseEvent>() {
  2. @Override
  3. public void handle(MouseEvent event) {
  4. if (event.isSecondaryButtonDown()) {
  5. conMenu.show(root, event.getScreenX(),
  6. event.getScreenY());
  7. }
  8. }
  9. });

使用setOnMousePressed()方法,我们为MouseEvent设置了一个事件处理器。 当我们单击第二个鼠标按钮(通常是右按钮)时,上下文菜单将显示在屏幕上。 它显示在鼠标单击的 x 和 y 坐标下方。

事件属性

以下程序探讨了MouseEvent的属性。 这是由于用户与鼠标交互而发生的事件。

EventSourceEx.java

  1. package com.zetcode;
  2. import javafx.application.Application;
  3. import javafx.event.EventHandler;
  4. import javafx.scene.Scene;
  5. import javafx.scene.input.MouseEvent;
  6. import javafx.scene.layout.Pane;
  7. import javafx.scene.shape.Rectangle;
  8. import javafx.stage.Stage;
  9. /**
  10. * ZetCode JavaFX tutorial
  11. *
  12. * This program explores the properties of
  13. * an event.
  14. *
  15. * Author: Jan Bodnar
  16. * Website: zetcode.com
  17. * Last modified: June 2015
  18. */
  19. public class EventSourceEx extends Application {
  20. @Override
  21. public void start(Stage stage) {
  22. initUI(stage);
  23. }
  24. private void initUI(Stage stage) {
  25. Pane root = new Pane();
  26. Rectangle rect = new Rectangle(30, 30, 80, 80);
  27. rect.setOnMouseClicked(new EventHandler<MouseEvent>() {
  28. @Override
  29. public void handle(MouseEvent e) {
  30. System.out.println(e.getSource());
  31. System.out.println(e.getTarget());
  32. System.out.println(e.getEventType());
  33. System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
  34. System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());
  35. }
  36. });
  37. root.getChildren().addAll(rect);
  38. Scene scene = new Scene(root, 300, 250);
  39. stage.setTitle("Event properties");
  40. stage.setScene(scene);
  41. stage.show();
  42. }
  43. public static void main(String[] args) {
  44. launch(args);
  45. }
  46. }

在示例中,我们有一个矩形。 我们将事件处理器添加到鼠标单击的事件类型。

  1. rect.setOnMouseClicked(new EventHandler<MouseEvent>() {
  2. @Override
  3. public void handle(MouseEvent e) {
  4. ...
  5. }
  6. });

setOnMouseClicked()将事件处理器添加到鼠标单击的事件类型。 处理器是一个匿名内部类。 当在矩形上检测到鼠标单击时,将调用其handle()方法。

  1. System.out.println(e.getSource());
  2. System.out.println(e.getTarget());
  3. System.out.println(e.getEventType());

这三个是通用属性,可用于所有事件。 getSource()方法返回最初发生事件的对象。 getTarget()方法返回此事件的事件目标。 在我们的例子中,事件源和事件目标是相同的-矩形。 getEventType()方法返回MouseEvent的事件类型。 在我们的情况下,它返回MOUSE_CLICKED值。

  1. System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
  2. System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());

这四个属性特定于此事件。 我们打印相对于场景和屏幕的鼠标单击的 x 和 y 坐标。

Lambda 表达式

从 JDK 8 开始,可以使用 lambda 表达式替换匿名内部类。

  1. rect.setOnMouseClicked((MouseEvent e) -> {
  2. System.out.println(e.getSource());
  3. System.out.println(e.getTarget());
  4. System.out.println(e.getEventType());
  5. System.out.format("x:%f, y:%f%n", e.getSceneX(), e.getSceneY());
  6. System.out.format("x:%f, y:%f%n", e.getScreenX(), e.getScreenY());
  7. });

这是使用 lambda 表达式重写的上一个示例中的事件处理代码。

通用处理器

在下一个示例中,我们创建一个监听所有类型事件的通用事件处理器。

GenericHandlerEx.java

  1. package com.zetcode;
  2. import javafx.application.Application;
  3. import javafx.event.Event;
  4. import javafx.event.EventHandler;
  5. import javafx.event.EventType;
  6. import javafx.scene.Scene;
  7. import javafx.scene.control.Button;
  8. import javafx.scene.layout.StackPane;
  9. import javafx.stage.Stage;
  10. /**
  11. * ZetCode JavaFX tutorial
  12. *
  13. * This program adds a generic event
  14. * handler to a button control.
  15. *
  16. * Author: Jan Bodnar
  17. * Website: zetcode.com
  18. * Last modified: June 2015
  19. */
  20. public class GenericHandlerEx extends Application {
  21. @Override
  22. public void start(Stage stage) {
  23. initUI(stage);
  24. }
  25. private void initUI(Stage stage) {
  26. StackPane root = new StackPane();
  27. Button btn = new Button("Button");
  28. btn.addEventHandler(EventType.ROOT, new GenericHandler());
  29. root.getChildren().add(btn);
  30. Scene scene = new Scene(root, 300, 250);
  31. stage.setTitle("Generic handler");
  32. stage.setScene(scene);
  33. stage.show();
  34. }
  35. public static void main(String[] args) {
  36. launch(args);
  37. }
  38. private class GenericHandler implements EventHandler<Event> {
  39. @Override
  40. public void handle(Event event) {
  41. System.out.println(event.getEventType());
  42. }
  43. }
  44. }

本示例具有一个按钮控件。 通用处理器已插入按钮。

  1. Button btn = new Button("Button");
  2. btn.addEventHandler(EventType.ROOT, new GenericHandler());

addEventHandler()方法将事件处理器注册到指定事件类型的按钮节点。 EventType.ROOT代表所有事件类型。

  1. private class GenericHandler implements EventHandler<Event> {
  2. @Override
  3. public void handle(Event event) {
  4. System.out.println(event.getEventType());
  5. }
  6. }

处理器使用其handle()方法将事件类型打印到控制台。

多种来源

可以将单个事件处理器添加到多个源。 可以使用getSource()方法确定事件的来源。

MultipleSourcesEx.java

  1. package com.zetcode;
  2. import javafx.application.Application;
  3. import javafx.event.ActionEvent;
  4. import javafx.event.EventHandler;
  5. import javafx.scene.Scene;
  6. import javafx.scene.control.Button;
  7. import javafx.scene.control.Label;
  8. import javafx.scene.layout.AnchorPane;
  9. import javafx.scene.layout.VBox;
  10. import javafx.stage.Stage;
  11. /**
  12. * ZetCode JavaFX tutorial
  13. *
  14. * This program plugs an EventHandler to multiple
  15. * controls.
  16. *
  17. * Author: Jan Bodnar
  18. * Website: zetcode.com
  19. * Last modified: June 2015
  20. */
  21. public class MultipleSourcesEx extends Application {
  22. private Label lbl;
  23. @Override
  24. public void start(Stage stage) {
  25. initUI(stage);
  26. }
  27. private void initUI(Stage stage) {
  28. AnchorPane root = new AnchorPane();
  29. VBox vbox = new VBox(5);
  30. Button btn1 = new Button("Close");
  31. Button btn2 = new Button("Open");
  32. Button btn3 = new Button("Find");
  33. Button btn4 = new Button("Save");
  34. MyButtonHandler mbh = new MyButtonHandler();
  35. btn1.setOnAction(mbh);
  36. btn2.setOnAction(mbh);
  37. btn3.setOnAction(mbh);
  38. btn4.setOnAction(mbh);
  39. vbox.getChildren().addAll(btn1, btn2, btn3, btn4);
  40. lbl = new Label("Ready");
  41. AnchorPane.setTopAnchor(vbox, 10d);
  42. AnchorPane.setLeftAnchor(vbox, 10d);
  43. AnchorPane.setBottomAnchor(lbl, 10d);
  44. AnchorPane.setLeftAnchor(lbl, 10d);
  45. root.getChildren().addAll(vbox, lbl);
  46. Scene scene = new Scene(root, 350, 200);
  47. stage.setTitle("Multiple sources");
  48. stage.setScene(scene);
  49. stage.show();
  50. }
  51. private class MyButtonHandler implements EventHandler<ActionEvent> {
  52. @Override
  53. public void handle(ActionEvent event) {
  54. Button btn = (Button) event.getSource();
  55. lbl.setText(String.format("Button %s fired", btn.getText()));
  56. }
  57. }
  58. public static void main(String[] args) {
  59. launch(args);
  60. }
  61. }

该示例有四个按钮和一个标签。 一个事件处理器将添加到所有四个按钮。 触发按钮的名称显示在标签中。

  1. Button btn1 = new Button("Close");
  2. Button btn2 = new Button("Open");
  3. Button btn3 = new Button("Find");
  4. Button btn4 = new Button("Save");

这四个按钮将共享一个事件处理器。

  1. MyButtonHandler mbh = new MyButtonHandler();

创建一个MyButtonHandler的实例。 它作为内部命名类实现。

  1. btn1.setOnAction(mbh);
  2. btn2.setOnAction(mbh);
  3. btn3.setOnAction(mbh);
  4. btn4.setOnAction(mbh);

使用setOnAction()方法将处理器添加到四个不同的按钮。

  1. private class MyButtonHandler implements EventHandler<ActionEvent> {
  2. @Override
  3. public void handle(ActionEvent event) {
  4. Button btn = (Button) event.getSource();
  5. lbl.setText(String.format("Button %s fired", btn.getText()));
  6. }
  7. }

MyButtonHandlerhandle()方法内部,我们确定事件的来源并使用来源的文本标签构建消息。 该消息通过setText()方法设置为标签控件。

JavaFX 事件 - 图1

图:多个来源

java.util.Timer

java.util.Timer计划任务以供将来在后台线程中执行。 TimerTask是可以计划为一次性执行或由计时器重复执行的任务。

TimerEx.java

  1. package com.zetcode;
  2. import java.util.Timer;
  3. import java.util.TimerTask;
  4. import javafx.application.Application;
  5. import javafx.application.Platform;
  6. import javafx.geometry.Insets;
  7. import javafx.scene.Scene;
  8. import javafx.scene.control.Alert;
  9. import javafx.scene.control.Button;
  10. import javafx.scene.control.Spinner;
  11. import javafx.scene.layout.HBox;
  12. import javafx.stage.Stage;
  13. /**
  14. * ZetCode JavaFX tutorial
  15. *
  16. * This program uses a java.util.Timer to
  17. * schedule a task.
  18. *
  19. * Author: Jan Bodnar
  20. * Website: zetcode.com
  21. * Last modified: June 2015
  22. */
  23. public class TimerEx extends Application {
  24. int delay = 0;
  25. @Override
  26. public void start(Stage stage) {
  27. initUI(stage);
  28. }
  29. private void initUI(Stage stage) {
  30. HBox root = new HBox(10);
  31. root.setPadding(new Insets(10));
  32. Timer timer = new java.util.Timer();
  33. Spinner spinner = new Spinner(1, 60, 5);
  34. spinner.setPrefWidth(80);
  35. Button btn = new Button("Show message");
  36. btn.setOnAction(event -> {
  37. delay = (int) spinner.getValue();
  38. timer.schedule(new MyTimerTask(), delay*1000);
  39. });
  40. root.getChildren().addAll(btn, spinner);
  41. stage.setOnCloseRequest(event -> {
  42. timer.cancel();
  43. });
  44. Scene scene = new Scene(root);
  45. stage.setTitle("Timer");
  46. stage.setScene(scene);
  47. stage.show();
  48. }
  49. private class MyTimerTask extends TimerTask {
  50. @Override
  51. public void run() {
  52. Platform.runLater(() -> {
  53. Alert alert = new Alert(Alert.AlertType.INFORMATION);
  54. alert.setTitle("Information dialog");
  55. alert.setHeaderText("Time elapsed information");
  56. String contxt;
  57. if (delay == 1) {
  58. contxt = "1 second has elapsed";
  59. } else {
  60. contxt = String.format("%d seconds have elapsed",
  61. delay);
  62. }
  63. alert.setContentText(contxt);
  64. alert.showAndWait();
  65. });
  66. }
  67. }
  68. public static void main(String[] args) {
  69. launch(args);
  70. }
  71. }

该示例有两个控件:一个按钮和一个微调器。 该按钮将启动计时器,延迟后将显示一个消息对话框。 延迟由微调控件选择。

  1. Timer timer = new java.util.Timer();

创建java.util.Timer的实例。

  1. Spinner spinner = new Spinner(1, 60, 5);

Spinner控件用于选择延迟量。 它的参数是最小值,最大值和当前值。 该值以毫秒为单位。

  1. btn.setOnAction(event -> {
  2. delay = (int) spinner.getValue();
  3. timer.schedule(new MyTimerTask(), delay*1000);
  4. });

在按钮的事件处理器中,我们使用getValue()方法获取微调框的当前值,并使用计时器的schedule()方法安排任务。

  1. stage.setOnCloseRequest(event -> {
  2. timer.cancel();
  3. });

当使用计时器的cancel()方法终止应用时,我们将取消计时器。

  1. private class MyTimerTask extends TimerTask {
  2. @Override
  3. public void run() {
  4. Platform.runLater(() -> {
  5. Alert alert = new Alert(Alert.AlertType.INFORMATION);
  6. alert.setTitle("Information dialog");
  7. alert.setHeaderText("Time elapsed information");
  8. String contxt;
  9. if (delay == 1) {
  10. contxt = "1 second has elapsed";
  11. } else {
  12. contxt = String.format("%d seconds have elapsed",
  13. delay);
  14. }
  15. alert.setContentText(contxt);
  16. alert.showAndWait();
  17. });
  18. }
  19. }

runLater()方法在 JavaFX 应用线程上执行任务。 我们显示一个消息对话框,通知您经过的时间。

JavaFX 事件 - 图2

图:经过的时间

移动窗口

以下示例显示了应用窗口在屏幕上的位置。

MovingWindowEx.java

  1. package com.zetcode;
  2. import javafx.application.Application;
  3. import javafx.beans.value.ChangeListener;
  4. import javafx.beans.value.ObservableValue;
  5. import javafx.geometry.Insets;
  6. import javafx.scene.Scene;
  7. import javafx.scene.control.Label;
  8. import javafx.scene.layout.VBox;
  9. import javafx.stage.Stage;
  10. /**
  11. * ZetCode JavaFX tutorial
  12. *
  13. * This program shows the screen coordinates
  14. * of the application window in two labels.
  15. *
  16. * Author: Jan Bodnar
  17. * Website: zetcode.com
  18. * Last modified: June 2015
  19. */
  20. public class MovingWindowEx extends Application {
  21. int x = 0;
  22. int y = 0;
  23. Label lblx;
  24. Label lbly;
  25. @Override
  26. public void start(Stage stage) {
  27. initUI(stage);
  28. }
  29. private void initUI(Stage stage) {
  30. VBox root = new VBox(10);
  31. root.setPadding(new Insets(10));
  32. String txt1 = String.format("x: %d", x);
  33. lblx = new Label(txt1);
  34. String txt2 = String.format("y: %d", y);
  35. lbly = new Label(txt2);
  36. root.getChildren().addAll(lblx, lbly);
  37. stage.xProperty().addListener(new ChangeListener<Number>() {
  38. @Override
  39. public void changed(ObservableValue<? extends Number> observable,
  40. Number oldValue, Number newValue) {
  41. doChange(newValue);
  42. }
  43. private void doChange(Number newValue) {
  44. x = newValue.intValue();
  45. updateXLabel();
  46. }
  47. });
  48. stage.yProperty().addListener(new ChangeListener<Number>() {
  49. @Override
  50. public void changed(ObservableValue<? extends Number> observable,
  51. Number oldValue, Number newValue) {
  52. doChange(newValue);
  53. }
  54. private void doChange(Number newValue) {
  55. y = newValue.intValue();
  56. updateYLabel();
  57. }
  58. });
  59. Scene scene = new Scene(root, 300, 250);
  60. stage.setTitle("Moving window");
  61. stage.setScene(scene);
  62. stage.show();
  63. }
  64. private void updateXLabel() {
  65. String txt = String.format("x: %d", x);
  66. lblx.setText(txt);
  67. }
  68. private void updateYLabel() {
  69. String txt = String.format("y: %d", y);
  70. lbly.setText(txt);
  71. }
  72. public static void main(String[] args) {
  73. launch(args);
  74. }
  75. }

该示例显示了两个标签控件中的当前窗口坐标。 为了获得窗口位置,我们监听舞台的xPropertyyProperty的变化。

  1. String txt1 = String.format("x: %d", x);
  2. lblx = new Label(txt1);
  3. String txt2 = String.format("y: %d", y);
  4. lbly = new Label(txt2);

这两个标签显示了应用窗口左上角的 x 和 y 坐标。

  1. stage.xProperty().addListener(new ChangeListener<Number>() {
  2. @Override
  3. public void changed(ObservableValue<? extends Number> observable,
  4. Number oldValue, Number newValue) {
  5. doChange(newValue);
  6. }
  7. private void doChange(Number newValue) {
  8. x = newValue.intValue();
  9. updateXLabel();
  10. }
  11. });

xProperty将舞台的水平位置存储在屏幕上。 我们添加一个ChangeListener来监听属性的更改。 每次修改属性时,我们都会检索新值并更新标签。

JavaFX 事件 - 图3

图:移动窗口

JavaFX 教程的这一部分专门讨论 JavaFX 事件。