跳转至

JavaFX 事件

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

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设置了一个事件处理器。 EventHandlerhandle()方法以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()));
    }
}

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

Multiple sources

图:多个来源

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 应用线程上执行任务。 我们显示一个消息对话框,通知您经过的时间。

Time elapsed

图:经过的时间

移动窗口

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

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);
    }
}

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

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来监听属性的更改。 每次修改属性时,我们都会检索新值并更新标签。

Moving a window

图:移动窗口

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



回到顶部