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