chapter01(简单窗口搭建及文件读写)
JavaFX
是一个用于构建富客户端应用程序的框架,提供了一种现代化的方式来创建桌面应用程序和互联网应用程序。
创建JavaFx项目
在Java体系中,最常用的图形界面设计库主要是Swing
和JavaFX
,本课程使用JavaFX,采用手写代码方式创建窗体界面,建议的jdk
版本为jdk8 (因为jdk8已经内置JavaFX库,高版本JDK中被剥离,需要额外下载jar 包),https://oc.gdufs.edu.cn 教学资源站点可下载JDK8(如果使用下面所述 的idea2018 整合版,则不用下载,整合版已经包含)。
- 启动
Intellij Idea
(以后简称 idea),新建项目之前,首先设置默认 的编码为utf-8 - 新建一个项目,如图1.3所示,左方选择“Java”,右方不要勾选任何Additional Libraries and Frameworks,按提示一路 next,选择一个合适的项目名完成新建过程,例如NetworkApp。
- 新建
JavaFX
源程序,命名为SimpleFX;鼠标右键点击对应的包chapter01,在弹出菜单中选择``New -> JavaFXApplication`,然后命名即可。
Application类
JavaFx
的Application类是主程序类,是JavaFX应用程序的入口点,里面封装了一系列成员方法.
launch()和start()方法
-
launch()方法: 启动 JavaFX 应用程序:
launch
方法负责初始化 JavaFX 应用程序的生命周期。它会调用Application
类的start
方法,这是你定义应用程序界面的地方。- 当调用
launch
方法时,JavaFX 会创建一个新的应用程序线程,并在该线程中调用start
方法。
- 当调用
-
start()方法,参数是一个
Stage primaryStage
对象,不需要你自己调用此函数,会被 launch 方法间接调用,用于初始化应用程序-
Stage primaryStage:这是应用程序的主窗口,所有的用户界面组件都将添加到这个窗口中
-
start
方法是应用程序的入口点,你可以在这里设置用户界面和处理事件。 -
在
start
方法执行完成后,JavaFX 应用程序会进入事件循环,等待用户交互。
-
通常在main函数中调用launch方法
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage; public class HelloWorld extends Application { @Override public void start(Stage primaryStage) { Button btn = new Button(); btn.setText("说 '你好'"); btn.setOnAction(event -> System.out.println("你好,世界!")); StackPane root = new StackPane(); root.getChildren().add(btn); Scene scene = new Scene(root, 300, 250); primaryStage.setTitle("Hello World!"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); // 启动 JavaFX 应用程序,调用 start 方法 }
}
setStyle()方法
几乎所有的JavaFx控件都可以使用 setStyle()方法 设置样式,参数为类似于CSS样式表语法的字符串
窗体创建
在SimpleFx类
中,可以定义成员变量,语法如private Button btnExit = new Button("退出");
设置窗口标题
在start方法中使用primaryStage.setTitle();
设置标题
Button按钮类
Button类
是JavaFx中非常常见的一个类,构造函数的参数是String类型,用于显示按钮的文本,或者可以通过setText()方法来修改里面的文字- setOnAction() 方法,用于处理按钮点击事件
在JavaFX中,处理按钮点击事件通常涉及到为按钮设置动作监听器。如果事件处理逻辑较为简单,直接在setOnAction
方法中通过匿名内部类或Lambda表达式来实现是一种非常简洁的方式。以下是如何在JavaFX中分别使用匿名内部类和Lambda表达式来处理四个按钮点击事件的示例。
使用匿名内部类
假设你有四个按钮button1
, button2
, button3
, button4
,你可以分别为它们设置动作监听器,如下所示:
button1.setOnAction(new EventHandler<ActionEvent>() {@Overridepublic void handle(ActionEvent event) {System.out.println("Button 1 clicked!");}
});button2.setOnAction(new EventHandler<ActionEvent>() {@Overridepublic void handle(ActionEvent event) {System.out.println("Button 2 clicked!");}
});// 对button3和button4的处理类似...
使用Lambda表达式
如果你的Java版本支持Lambda表达式(Java 8及以上),并且事件处理逻辑简单,那么使用Lambda表达式可以使得代码更加简洁。以下是如何使用Lambda表达式来设置相同按钮的动作监听器:
button1.setOnAction(event -> System.out.println("Button 1 clicked!"));button2.setOnAction(event -> System.out.println("Button 2 clicked!"));// 对button3和button4的处理类似...
单独写一个内部类
如果事件处理逻辑较为复杂,或者需要在多个地方重用相同的处理逻辑,那么将事件处理逻辑封装到一个单独的内部类中会是一个更好的选择。以下是如何实现这一点的示例:
class MyButtonHandler implements EventHandler<ActionEvent> {private String buttonName;public MyButtonHandler(String buttonName) {this.buttonName = buttonName;}@Overridepublic void handle(ActionEvent event) {System.out.println(buttonName + " clicked!");// 这里可以添加更复杂的逻辑}
}// 然后为按钮设置动作监听器
button1.setOnAction(new MyButtonHandler("Button 1"));
button2.setOnAction(new MyButtonHandler("Button 2"));// 对button3和button4的处理类似...
通过这种方式,你可以将事件处理逻辑从界面构建代码中分离出来,使得代码更加模块化和易于维护。同时,通过为MyButtonHandler
类添加更多的属性和方法,你还可以扩展其功能,以支持更复杂的事件处理场景。
Lable类
new Label("信息显示区:")
创建一个显示信息的区域
TextField文本编辑框
private TextField tfSend = new TextField();
方法用于创建一个文本编辑框
TextArea文本显示框
private TextArea taDisplay= new TextArea();
方法用于创建一个文本显示框
- setEditable()方法: 设置是否只读属性
VBox垂直布局(容器类)
VBox
是 JavaFX 中的一个布局容器类,用于垂直排列其子节点(通过vBox.getChildren().addAll()
方法)。它是 javafx.scene.layout
包的一部分,提供了一种简单的方式来组织和管理用户界面组件的布局
1. 基本特性
- 垂直排列:
VBox
会将其子节点从上到下垂直排列,子节点的顺序与添加顺序相同。 - 自动调整大小:
VBox
会根据其子节点的大小自动调整自身的宽度和高度。 - 间距和对齐:可以设置子节点之间的间距以及对齐方式。
2. 常用属性
- spacing:设置子节点之间的间距(以像素为单位)。
- alignment:设置子节点的对齐方式,例如顶部对齐、底部对齐、居中对齐等。
- padding:设置
VBox
内部边距,控制内容与边框之间的距离。(类似于CSS中的padding属性)
3. 对齐方式
VBox
的对齐方式可以通过 setAlignment
方法设置,常用的对齐方式包括:
Pos.TOP_LEFT
、Pos.TOP_CENTER
、Pos.TOP_RIGHT
、Pos.CENTER_LEFT
、Pos.CENTER
、Pos.CENTER_RIGHT
、Pos.BOTTOM_LEFT
、Pos.BOTTOM_CENTER
、Pos.BOTTOM_RIGHT
HBox(水平容器)
类似于上面的VBox,只不过是水平排列子元素
窗体布局代码
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;public class SimpleFx extends Application {private Button btnExit = new Button("退出");private Button btnSend = new Button("发送");private Button btnOpen = new Button("加载");private Button btnSave = new Button("保存");//待发送信息的文本框private TextField tfSend = new TextField();//显示信息的文本区域private TextArea taDisplay = new TextArea();public void start(Stage primaryStage) {BorderPane mainPane = new BorderPane();//内容显示区域VBox vBox = new VBox();vBox.setSpacing(10);//各控件之间的间隔//VBox面板中的内容距离四周的留空区域vBox.setPadding(new Insets(10, 20, 10, 20));vBox.getChildren().addAll(new Label("信息显示区:"), taDisplay, new Label("信息输入区"), tfSend);//设置显示信息区的文本区域可以纵向自动扩充范围VBox.setVgrow(taDisplay, Priority.ALWAYS);taDisplay.setEditable(false);taDisplay.setStyle("-fx-wrap-text: true; /* 实际上是默认的 */ -fx-font-size: 14px;");mainPane.setCenter(vBox); // 设置文本只读和自动换行//底部按钮区域HBox hBox = new HBox();hBox.setSpacing(10);hBox.setPadding(new Insets(10, 20, 10, 20));// 设置按钮的交互效果btnExit.setOnAction(event -> {System.exit(0);});btnSend.setOnAction(event -> {String msg = tfSend.getText();taDisplay.appendText(msg + "\n");tfSend.clear();});tfSend.setOnKeyPressed(event -> {if (event.getCode() == KeyCode.ENTER) {String text = tfSend.getText();String prefix = event.isShiftDown() ? "echo: " : "";taDisplay.appendText(prefix + text + "\n");tfSend.setText(""); // 清空文本框}});hBox.setAlignment(Pos.CENTER_RIGHT);hBox.getChildren().addAll(btnSend, btnSave, btnOpen, btnExit);mainPane.setBottom(hBox);Scene scene = new Scene(mainPane, 700, 400);primaryStage.setScene(scene);primaryStage.show();}public static void main(String[] args) {launch(args);}}
文本读写
- 新增一个文件操作类
TextFileIO
,负责文件操作的相关功能,至少实现 append方法和load方法用于保存和读取文件; - 在
SimpleFX类
中的合适位置将TextFileIO类实例化为textFileIO,在“保存”按钮的响应事件代码中添加相应功能
FileChooser类
FileChooser fileChooser = new FileChooser();
File file = fileChooser.showSaveDialog(null);
FileChooser
类是一个用于让用户通过图形用户界面(GUI)选择文件的工具。你提供的代码片段中,FileChooser
被实例化,并调用其 showSaveDialog(null)
方法来显示一个保存文件的对话框。
这里的 null
参数传递给 showSaveDialog
方法,意味着没有将对话框的父窗口(或所有者)设置为特定的窗口。这通常意味着对话框会作为一个顶级窗口出现,不依赖于任何其他窗口。
showSaveDialog
方法会阻塞当前线程(在JavaFX应用中通常是JavaFX应用线程),直到用户关闭对话框。根据用户的操作,该方法会返回:
- 如果用户选择了文件并点击了“保存”或类似的确认按钮,则返回一个代表用户所选文件的
File
对象。 - 如果用户取消了操作(例如,点击了“取消”按钮或关闭了对话框),则返回
null
PrintWriter类
- new FileOutputStream(file, true)
- 创建一个
FileOutputStream
对象,该对象用于将字节写入到指定的文件(file
)。 - 第二个参数
true
表示以追加模式打开文件。如果文件不存在,则尝试创建该文件。如果文件已存在,则写入的数据会被追加到文件内容的末尾,而不是覆盖原有内容。
- 创建一个
- new OutputStreamWriter(…, “UTF-8”)
- 创建一个
OutputStreamWriter
对象,该对象是将字符流转换为字节流的桥梁。它使用指定的字符编码(在这个例子中是 “UTF-8”)将字符转换为字节。 - 它接收一个
OutputStream
对象(在这个例子中是FileOutputStream
)作为参数。
- 创建一个
- new PrintWriter(…)
- 创建一个
PrintWriter
对象,该对象可以向文本输出流打印字符的便捷方式。 - 它接收一个
Writer
对象(在这个例子中是OutputStreamWriter
)作为参数
- 创建一个
PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8"));
这行代码就是创建一个PrintWriter对象,可以将字符转变成字节在写入到文件中;
StringBuilder类
StringBuilder
是 Java 中的一个可变字符序列类,它位于 java.lang
包中。与 String
类不同,StringBuilder
的内容是可以修改的。当你需要频繁地对字符串进行修改(如拼接、删除、替换等操作)时,使用 StringBuilder
会比使用 String
更高效,因为 String
是不可变的,每次修改都会生成一个新的字符串对象,而 StringBuilder
的修改是在原对象上进行的,不会创建新的对象。
StringBuilder
提供了一系列的方法来操作字符序列,比如:
append(CharSequence csq)
: 将指定的字符序列追加到此字符序列的末尾。append(CharSequence csq, int start, int end)
: 追加指定CharSequence
的子序列到此序列的末尾。insert(int offset, char c)
: 在此序列的指定位置插入指定的char
值。delete(int start, int end)
: 移除此序列的子字符串中的字符。replace(int start, int end, String str)
: 用给定String
中的字符替换此序列的子字符串中的字符。
StringBuilder
的构造方法有几个版本,最常用的两个是:
StringBuilder()
: 构造一个空的StringBuilder
。StringBuilder(int capacity)
: 构造一个具有指定初始容量的StringBuilder
。这里的容量指的是StringBuilder
可以容纳的字符数,但它会自动扩容以容纳更多的字符。
示例代码:
StringBuilder sb = new StringBuilder(); // 创建一个空的StringBuilder
sb.append("Hello, "); // 追加字符串
sb.append("World!"); // 继续追加字符串
System.out.println(sb.toString()); // 输出: Hello, World!// 使用指定容量的构造方法
StringBuilder sbWithCapacity = new StringBuilder(100); // 初始容量为100
sbWithCapacity.append("This is a longer string than the previous one.");
System.out.println(sbWithCapacity.toString()); // 输出: This is a longer string than the previous one.
StringBuilder
是线程不安全的,如果在多线程环境下需要频繁操作字符串,建议使用 StringBuffer
,它是 StringBuilder
的线程安全版本。但在单线程环境下,StringBuilder
的性能通常比 StringBuffer
要好。
文本读写部分代码
import javafx.stage.FileChooser;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Scanner;public class TextFileIO {// 注意:这里移除了private PrintWriter pw; 和 private Scanner sc; 字段,因为我们在方法内部管理它们 // 内容添加到文件中,文件通过对话框来确定public TextFileIO(){}; // 空构造方法public void append(String msg) {FileChooser fileChooser = new FileChooser();File file = fileChooser.showSaveDialog(null);if (file == null) { // 用户放弃操作则返回 return;}// 使用try-with-resources语句自动关闭资源 try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8"))) {pw.println(msg);} catch (IOException e) {e.printStackTrace();}}// 从文件中加载内容 public String load() {FileChooser fileChooser = new FileChooser();File file = fileChooser.showOpenDialog(null);if (file == null) { // 用户放弃操作则返回 return null;}StringBuilder sb = new StringBuilder();try (Scanner sc = new Scanner(file, "UTF-8")) {while (sc.hasNextLine()) { // 使用hasNextLine()确保换行符不会重复添加 sb.append(sc.nextLine()).append("\n"); // 补上行读取的行末尾回车 }} catch (IOException e) {e.printStackTrace();}// 移除字符串末尾可能存在的多余换行符 if (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') {sb.setLength(sb.length() - 1);}return sb.toString();}
}