Java作业-5

Javafx事件驱动和动画

什么是事件源对象?什么是事件对象?描述事件源对象和事件对象之间的关系

  • 事件源对象是触发特定事件的GUI组件,比如按钮(Button)、文本框(TextField)等。当用户与这些组件进行交互(如点击按钮)时,该组件就会生成一个事件对象并将其发送到事件队列。

  • 事件对象是一个封装了与特定事件相关信息的对象。它通常是Event类或其子类的一个实例。事件对象包含了事件源、事件类型、时间戳等信息,这些信息有助于事件处理器确定如何响应该事件。

  • 事件是从一个事件源上产生的对象:当一个事件在事件源对象上被触发时,相应的事件处理函数会被调用,并接收一个事件对象作为参数。这个事件对象包含了事件源对象以及其他有关事件的信息。

    • 触发事件:当在事件源对象(如按钮)上发生某个动作(如点击)时,会创建一个与之相关的事件对象。
    • 传递信息:事件对象携带了事件的各种信息,包括哪个对象是事件源。这对于后续的事件处理是非常有用的。
    • 事件队列与监听器:事件对象被发送到事件队列,并最终被传递到注册在该事件源对象上的事件处理器(或称为监听器)。
    • 响应事件:事件处理器(监听器)根据事件对象的信息来决定如何响应。比如,你可能会检查事件类型或者事件源对象的状态,然后执行相应的代码。
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        Button btn = new Button("Click me");
        
        // 设置事件处理器
        btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                System.out.println("Button clicked!");
                // 在这里,`event` 就是事件对象,它包含了事件的信息
                // `btn` 是事件源对象
            }
        });
        
        StackPane root = new StackPane();
        root.getChildren().add(btn);
        
        Scene scene = new Scene(root, 300, 250);
        
        primaryStage.setTitle("Event Handling Example");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

在这个例子中,Button对象btn就是事件源对象,当点击这个按钮时,会生成一个ActionEvent事件对象并传递给handle方法。

如何注册一个处理器对象,以及如何实现一个处理器接口?

  • 在Java8及以上版本中可以使用Lambda表达式来注册事件处理器
Button btn = new Button("Click me");
btn.setOnAction(e -> {
    System.out.println("Button clicked!");
    // 这里的 `e` 是一个 ActionEvent 对象,包含了事件的信息。
});
  • 通过实现特定的处理器接口来创建一个事件处理器类。通常,这个接口只有一个需要实现的方法,用于处理事件。
public class Main extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        Button btn = new Button("Click me");

        // 创建并注册事件处理器
        btn.setOnAction(new MyButtonHandler());

        StackPane root = new StackPane();
        root.getChildren().add(btn);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Event Handling Example with Interface");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    // 实现 EventHandler 接口
    class MyButtonHandler implements EventHandler<ActionEvent> {
        @Override
        public void handle(ActionEvent event) {
            System.out.println("Button clicked!");
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

注意:

  • 代码复用:如果一个处理器需要被多个事件源共用,实现处理器接口是个不错的选择。
  • 简洁性:对于简单的一次性用途,Lambda表达式通常更简洁。
  • 类型安全:实现接口能在编译时检查类型,提高代码的安全性。
  • 可读性和维护性:如果事件处理逻辑非常复杂,使用实现接口的方式可能会使代码更易于阅读和维护。

EventHandler<ActionEvent> 接口的处理器方法是什么?

该方法叫做handle,用于处理与其关联的事件类型。这个方法接受一个ActionEvent类型的参数,这个参数包含了事件相关的所有信息。
void handle(ActionEvent event);
event: 这是一个ActionEvent对象,包含了触发该事件的所有相关信息,比如事件源、事件类型等。

import javafx.event.ActionEvent;
import javafx.event.EventHandler;

class MyButtonHandler implements EventHandler<ActionEvent> {
    @Override
    public void handle(ActionEvent event) {
        // 在这里写下你的事件处理代码
        System.out.println("Button clicked!");
    }
}
Button btn = new Button("Click Me");
btn.setOnAction(new MyButtonHandler());
  • 由于这个接口是一个函数式接口(只有一个需要实现的方法),可以使用Lambda表达式来更简洁地实现。
  • 如果事件处理逻辑比较简单,直接使用Lambda表达式通常更简洁;但如果事件处理逻辑更复杂,或者需要在多处重用,那么创建一个实现了EventHandler<ActionEvent>接口的类可能会更合适。

对一个按钮注册一个ActionEvent 处理器的注册方法是什么?

使用setOnAction方法。这个方法接受一个实现了EventHandler<ActionEvent>接口的对象作为参数。下面是几种不同的方式来注册事件处理器。

使用匿名内部类
可以使用匿名内部类的方式创建一个EventHandler<ActionEvent>对象并注册。

Button btn = new Button("Click Me");
btn.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        System.out.println("Button clicked!");
    }
});

使用Lambda表达式
如果使用的是Java 8或更高版本,可以使用Lambda表达式来更简洁地注册事件处理器。

Button btn = new Button("Click Me");
btn.setOnAction(e -> System.out.println("Button clicked!"));

使用已存在的对象
如果已经有一个实现了EventHandler<ActionEvent>接口的对象,可以直接将它注册为按钮的事件处理器。

class MyButtonHandler implements EventHandler<ActionEvent> {
    @Override
    public void handle(ActionEvent event) {
        System.out.println("Button clicked!");
    }
}

// 在其他地方的代码
Button btn = new Button("Click Me");
MyButtonHandler handler = new MyButtonHandler();
btn.setOnAction(handler);

使用方法引用
如果有一个符合void method(ActionEvent e)签名的方法,可以使用方法引用来注册事件处理器。

public class MyClass {
    public void myMethod(ActionEvent e) {
        System.out.println("Button clicked!");
    }
}

// 在其他地方的代码
MyClass obj = new MyClass();
Button btn = new Button("Click Me");
btn.setOnAction(obj::myMethod);

如果类 A 是类 B 中的一个内部类,A 的类文件名字是什么?如果类 B 包含两个匿名内部类,这两个类的 .class 文件名是什么?

如果有一个外部类B和它的一个内部类A,则内部类A的.class文件名通常会是B$A.class。
例如,如果有这样一个类定义:

public class B {
    public class A {
        // ...
    }
}

编译后会得到两个.class文件:
B.class —— 对应于外部类B
B$A.class —— 对应于内部类A

匿名内部类的命名规则稍有不同。如果一个类B包含两个匿名内部类,那么这两个匿名内部类的.class文件通常会按照它们在代码中出现的顺序被命名为B$1.class和B$2.class。
例如:

public class B {
    Runnable r1 = new Runnable() {
        public void run() {
            // ...
        }
    };

    Runnable r2 = new Runnable() {
        public void run() {
            // ...
        }
    };
}

编译后会得到三个.class文件:

B.class —— 对应于外部类B
B$1.class —— 对应于第一个匿名内部类
B$2.class —— 对应于第二个匿名内部类

这样的命名规则有助于JVM和Java运行时系统识别和管理各个类。

下面代码中的错误是什么?

public class Test extends Application{
    public void start(Stage stage){
        Button btOK=new Button("OK");
        btOK.setOnAction(new EventHandler<ActionEvent>){
            public void handle(ActionEvent e){
                System.out.println(e.getSource());
            }
        }
    }  // Something missing here
}

修改后代码:

public class Test extends Application {
    @Override
    public void start(Stage stage) {
        Button btOK = new Button("OK");
        btOK.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {
                System.out.println(e.getSource());
            }
        });  // 添加了分号
    }  // 添加了结束括号
}

(选取4张卡牌)请写一个程序,可以让用户通过单击Refresh 按钮以显示从一副52张卡牌选取的4张卡牌

import java.util.Random;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class DrawCards extends Application {
    @Override
    public void start(Stage primaryStage) {
        VBox vBox = new VBox(10);
        HBox hBox = new HBox(10);
        GridPane cardPane = getRandomCard(4);
        cardPane.setHgap(10);

        hBox.getChildren().add(cardPane);
        Button refreshButton = new Button("Refresh");

        // 不能直接用cardPane=getRandomCard(4);
        // refreshButton.setOnAction(e -> {
        //     cardPane=getRandomCard(4);
        // });
        /*这是一个经典的Java闭包问题。
        Lambda表达式中使用的变量必须是final或者“effectively final”。
        也就是说,该变量在初始化后不能再被修改。
        cardPane在Lambda表达式里被重新赋值,所以编译器会报错。
        可以这么解决*/
        
        // 1.修改逻辑,删去原来的hBox,重新生成一个hBox
        refreshButton.setOnAction(e -> {
            hBox.getChildren().clear();
            GridPane newCardPane = getRandomCard(4);
            newCardPane.setHgap(10);
            hBox.getChildren().add(newCardPane);
        });

        // 2.使用数组或容器封装 cardPane
        // final GridPane[] cardPaneContainer = { getRandomCard(4) };
        
        // cardPaneContainer[0].setHgap(10);
        // Button refreshButton = new Button("Refresh");
        // refreshButton.setOnAction(e -> {
        //     cardPaneContainer[0] = getRandomCard(4);
        //     hBox.getChildren().clear();
        //     cardPaneContainer[0].setHgap(10);
        //     hBox.getChildren().add(cardPaneContainer[0]);
        // });

        
        vBox.getChildren().addAll(hBox, refreshButton);
        vBox.setAlignment(Pos.CENTER);
        hBox.setAlignment(Pos.CENTER);

        Scene scene = new Scene(vBox, 360, 200, false, null);

        primaryStage.setScene(scene);
        primaryStage.setTitle("Draw Cards");
        primaryStage.show();
    }

    private GridPane getRandomCard(int n) {
        GridPane cardPane = new GridPane();

        Random randomInt = new Random();
        int[] cardList = new int[n];
        for (int i = 0; i < n; i++) {
            int nextInt = randomInt.nextInt(52) + 1;
            for (int card : cardList) {
                while (nextInt == card) {
                    nextInt = randomInt.nextInt(52) + 1;
                }
                cardList[i] = nextInt;
            }
        }

        for (int i = 0; i < n; i++) {
            String cardImgPath = "E:/Repos/Course/Java/my/code/W3/demo/src/card/" + cardList[i] + ".png";
            ImageView imgView = new ImageView(new Image(cardImgPath));
            cardPane.add(imgView, i, 0);
        }
        return cardPane;
    }
}

(旋转一个四边形)请写一个程序,在Rotate 按钮被单击时,将一个四边形向右旋转15度

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class RotatingRectangle extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Rectangle rec = new Rectangle(0, 0, 100, 162);

        rec.setStyle("-fx-fill: white; -fx-stroke: black;"); // 黑色画线,白色填充
        Button rotateButton = new Button("Rotate");
        rotateButton.setOnAction(e -> {
            // 在原有角度的基础上旋转15度
            rec.setRotate(rec.getRotate() + 15);
        });
        VBox vBox = new VBox(50); // 防止矩形被按钮覆盖
        vBox.getChildren().addAll(rec, rotateButton);
        HBox hBox = new HBox(vBox);

        vBox.setAlignment(Pos.CENTER);
        hBox.setAlignment(Pos.CENTER);

        Scene scene = new Scene(hBox, 486, 300, false, null);
        primaryStage.setScene(scene);
        primaryStage.setTitle("RotatingRectangle");
        primaryStage.show();
    }

}

(移动小球)编写一个程序,在面板上移动小球。应该定义一个面板类来显示小球,并提供向左、向右、向上和向下移动小球的方法。请进行边界检査以防止球完全移到视线之外

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class MoveBall extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Button leftBtn = new Button("Left");
        Button rightBtn = new Button("Right");
        Button upBtn = new Button("Up");
        Button downBtn = new Button("Down");

        Circle circle = new Circle(0, 0, 20);
        circle.setStyle("-fx-fill: white; -fx-stroke: black;"); // 黑色画线,白色填充

        HBox hBox = new HBox(10);
        VBox vBox = new VBox(100);

        hBox.getChildren().addAll(leftBtn, rightBtn, upBtn, downBtn);
        vBox.getChildren().addAll(circle, hBox);

        // vBox.setAlignment(Pos.CENTER);
        hBox.setAlignment(Pos.CENTER);

        Scene scene = new Scene(vBox, 200, 200, false, null);

        leftBtn.setOnAction(e -> {
            moveNodeWithinBounds(circle, -10, 0, scene.getWidth(), scene.getHeight());
        });
        rightBtn.setOnAction(e -> {
            moveNodeWithinBounds(circle, 10, 0, scene.getWidth(), scene.getHeight());
        });
        upBtn.setOnAction(e -> {
            moveNodeWithinBounds(circle, 0, -10, scene.getWidth(), scene.getHeight());
        });
        downBtn.setOnAction(e -> {
            moveNodeWithinBounds(circle, 0, 10, scene.getWidth(), scene.getHeight());
        });

        primaryStage.setScene(scene);
        primaryStage.setTitle("Rotating Rectangle");
        primaryStage.show();
    }

    private void moveNodeWithinBounds(Node node, double dx, double dy, double sceneWidth, double sceneHeight) {
        double newX = node.getTranslateX() + dx;
        double newY = node.getTranslateY() + dy;

        double nodeWidth = node.getBoundsInParent().getWidth();
        double nodeHeight = node.getBoundsInParent().getHeight();

        if (newX + nodeWidth > 0 && newX < sceneWidth) {
            node.setTranslateX(newX);
        }

        if (newY + nodeHeight > 0 && newY < sceneHeight) {
            node.setTranslateY(newY);
        }
    }
    
}