JavaFX记事本
- JavaFX实现记事本
- 一、需求说明
- 二、功能演示
- 三、 设计流程
- 四、实现代码
JavaFX实现记事本
一、需求说明
记事本程序可实现以下功能:
- 文件新建
- 文件保存
- 文件打开
- 撤销,回复
- 灰度
- 查找
- 替换
- 字体
- 修改前保
- 状态栏
- 右键
- 自动换行
二、功能演示
- 文件新建
- 文件保存
- 文件打开
- 撤销,回复
- 灰度
在字体框中没有内容时,无法使用查找与替换功能 - 查找
- 替换
- 字体
整体框图
字体->选择字体
字形->选择粗体和斜体
大小->选择字号
示例->展示当前所选的字体是什么样字 - 修改前保
如果文本框中有内容但无存储路径,则在打开和新建之前将询问用户是否保存
如果文本框中有内容且有存储路径,则在打开和新建之前将替用户将内容保存到当前路径下的txt文件中 - 状态栏
可以选择是否可视,状态栏显示当前光标位于第几个字符的位置 - 右键
在文本框点击鼠标右键可出现右键菜单 - 自动换行
可选是否自动换行,若选择自动换行则超出文本框显示部分的文本将自动显示到记事本下一行的位置,效果如下图
三、 设计流程
结构图
界面设计
界面设计使用JavaFX Scene Builder 2.0完成
记事本主界面设计
字体选择框设计
功能实现
实现对txt文件的读取操作
实现记事本的主要功能
实现记事本字体选择功能
加载记事本场景
启动场景
四、实现代码
注释中说明了每段代码的功能
package application;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
public class FileTools {
//读文件
public static String readFile(File file) {
StringBuilder resultStr = new StringBuilder();
try {
BufferedReader bReader = new BufferedReader(new FileReader(file));
String line = bReader.readLine();
while (line != null) {
resultStr.append(line+"\n");
line = bReader.readLine();
}
bReader.close();
} catch (Exception e) {
e.printStackTrace();
}
return resultStr.toString();
}
//写文件
public static void writeFile(File file, String str) {
try {
BufferedWriter bWriter = new BufferedWriter(new FileWriter(file));
bWriter.write(str);
bWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package application;
import java.io.File;
import java.io.IOException;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class NoteBookController {
@FXML
private MenuItem SaveMenu;
@FXML
private MenuItem FindMenu;
@FXML
private CheckMenuItem WrapMenu;
@FXML
private AnchorPane layoutPane;
@FXML
private MenuItem ReplaceMenu;
@FXML
private CheckMenuItem StateMenu;
@FXML
private MenuItem OpenMenu;
@FXML
private MenuItem TypefaceMenu;
@FXML
private MenuItem NewMenu;
@FXML
private TextArea ta;
@FXML
private Label label;
@FXML
private MenuItem Redo;
@FXML
private MenuItem Undo;
private File result;
// 开始搜索的位置
int startIndex = 0;
// textarea中光标的位置
int position = 0;
// 灰度控制
public void initialize() {
// 初始状态下不可使用查找与替换功能
FindMenu.setDisable(true);
ReplaceMenu.setDisable(true);
//初始状态不可使用撤销和重做功能
Redo.setDisable(true);
Undo.setDisable(true);
// 状态栏不可视
label.setVisible(false);
ta.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
position = ta.getCaretPosition();
label.setText("第" + position + "个字符");
}
});
// 对textarea的内容是否改变进行监听
ta.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
// 如果testarea中内容部位空,则可以使用查找与替换
if (ta.getLength() > 0) {
FindMenu.setDisable(false);
ReplaceMenu.setDisable(false);
} // 否则禁用查找与替换
else {
FindMenu.setDisable(true);
ReplaceMenu.setDisable(true);
}
Redo.setDisable(false);
Undo.setDisable(false);
// 光标位置
position = ta.getCaretPosition();
label.setText("第" + position + "个字符");
}
});
}
// 修改前保
void saveadvance() {
if (result != null && ta.getLength() > 0) {
FileTools.writeFile(result, ta.getText());
} else if (result == null && ta.getLength() > 0) {
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt");
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setTitle("保存当前内容");
result = fileChooser.showSaveDialog(null);
if (result != null) {
FileTools.writeFile(result, ta.getText());
}
}
}
// 新建功能
@FXML
void onNewMenu(ActionEvent event) {
// 新建前保存
saveadvance();
ta.clear();
result = null;
}
// 打开功能
@FXML
void onOpenMenu(ActionEvent event) {
// 打开前保存
saveadvance();
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt");
fileChooser.getExtensionFilters().add(extFilter);
result = fileChooser.showOpenDialog(null);
if (result != null) {
ta.setText(FileTools.readFile(result));
}
}
// 保存功能
@FXML
void onSaveMenu(ActionEvent event) throws IOException {
if (result != null)// 如果已经存在保存路径
{
FileTools.writeFile(result, ta.getText());
} else// 如果不存在保存的路径
{
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt");
fileChooser.getExtensionFilters().add(extFilter);
result = fileChooser.showSaveDialog(null);
if (result != null) {
FileTools.writeFile(result, ta.getText());
}
}
}
//撤销
@FXML
void onUndoMenu(ActionEvent event) {
ta.undo();
Undo.setDisable(true);
}
//重做
@FXML
void onRedoMenu(ActionEvent event) {
ta.redo();
Redo.setDisable(true);
}
// 查找功能
@FXML
void onFindMenu(ActionEvent event) throws IOException {
HBox h1 = new HBox();
h1.setPadding(new Insets(20, 5, 20, 5));
h1.setSpacing(5);
Label lable1 = new Label("查找内容(N):");
TextField tf1 = new TextField();
h1.getChildren().addAll(lable1, tf1);
VBox v1 = new VBox();
v1.setPadding(new Insets(20, 5, 20, 10));
Button btn1 = new Button("查找下一个(F)");
v1.getChildren().add(btn1);
HBox findRootNode = new HBox();
findRootNode.getChildren().addAll(h1, v1);
Stage findStage = new Stage();
Scene scene1 = new Scene(findRootNode, 450, 90);
findStage.setTitle("查找");
findStage.setScene(scene1);
findStage.setResizable(false); // 固定窗口大小
findStage.show();
btn1.setOnAction((ActionEvent e) -> {
String textString = ta.getText(); // 获取记事本文本域的字符串
String tfString = tf1.getText(); // 获取要查找的字符串
if (!tf1.getText().isEmpty()) {
if (textString.contains(tfString)) {
// 查找方法
if (startIndex == -1) {// not found
Alert alert1 = new Alert(AlertType.WARNING);
alert1.titleProperty().set("提示");
alert1.headerTextProperty().set("已经找不到相关内容了!");
alert1.show();
}
startIndex = ta.getText().indexOf(tf1.getText(), startIndex);
if (startIndex >= 0 && startIndex < ta.getText().length()) {
ta.selectRange(startIndex, startIndex + tf1.getText().length());
startIndex += tf1.getText().length();
}
}
if (!textString.contains(tfString)) {
Alert alert1 = new Alert(AlertType.WARNING);
alert1.titleProperty().set("提示");
alert1.headerTextProperty().set("找不到相关内容!");
alert1.show();
}
} else if (tf1.getText().isEmpty()) {
Alert alert1 = new Alert(AlertType.WARNING);
alert1.titleProperty().set("出错了");
alert1.headerTextProperty().set("输入内容为空");
alert1.show();
}
});
}
// 替换功能
@FXML
void onReplaceMenu(ActionEvent event) throws IOException {
HBox h1 = new HBox();
h1.setPadding(new Insets(20, 5, 10, 8));
h1.setSpacing(5);
Label label1 = new Label("查找下一个(F)");
TextField tf1 = new TextField();
h1.getChildren().addAll(label1, tf1);
HBox h2 = new HBox();
h2.setPadding(new Insets(5, 5, 20, 8));
h2.setSpacing(5);
Label label2 = new Label("替换内容(N):");
TextField tf2 = new TextField();
h2.getChildren().addAll(label2, tf2);
VBox v1 = new VBox();
v1.getChildren().addAll(h1, h2);
VBox v2 = new VBox();
v2.setPadding(new Insets(21, 5, 20, 10));
v2.setSpacing(13);
Button btn1 = new Button("查找下一个");
Button btn2 = new Button("替换为");
v2.getChildren().addAll(btn1, btn2);
HBox replaceRootNode = new HBox();
replaceRootNode.getChildren().addAll(v1, v2);
Stage replaceStage = new Stage();
Scene scene = new Scene(replaceRootNode, 430, 120);
replaceStage.setTitle("替换");
replaceStage.setScene(scene);
replaceStage.setResizable(false); // 固定窗口大小
replaceStage.show();
btn1.setOnAction((ActionEvent e) -> {
String textString = ta.getText(); // 获取记事本文本域的字符串
String tfString = tf1.getText(); // 获取查找内容的字符串
if (!tf1.getText().isEmpty()) {
if (textString.contains(tfString)) {
if (startIndex == -1) {// not found
Alert alert1 = new Alert(AlertType.WARNING);
alert1.titleProperty().set("提示");
alert1.headerTextProperty().set("已经找不到相关内容了!");
alert1.show();
}
startIndex = ta.getText().indexOf(tf1.getText(), startIndex);
if (startIndex >= 0 && startIndex < ta.getText().length()) {
ta.selectRange(startIndex, startIndex + tf1.getText().length());
startIndex += tf1.getText().length();
}
btn2.setOnAction((ActionEvent e2) -> {
ta.replaceSelection(tf2.getText());
});
}
if (!textString.contains(tfString)) {
Alert alert1 = new Alert(AlertType.WARNING);
alert1.titleProperty().set("提示");
alert1.headerTextProperty().set("找不到相关内容!");
alert1.show();
}
} else if (tf1.getText().isEmpty()) {
Alert alert1 = new Alert(AlertType.WARNING);
alert1.titleProperty().set("出错了");
alert1.headerTextProperty().set("输入内容为空");
alert1.show();
}
});
}
// 自动换行
@FXML
void onWrapMenu(ActionEvent event) {
if (WrapMenu.isSelected())
ta.setWrapText(true);
else
ta.setWrapText(false);
}
// 字体
@FXML
void onTypefaceMenu(ActionEvent event) throws IOException {
Stage stage = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource("Typeface.fxml"));
Parent parent = loader.load();
Scene scene = new Scene(parent);
stage.setScene(scene);
// 等待窗口被关闭在做出下一步反应
stage.showAndWait();
if (scene.getUserData() != null)// 如果用户有设定字体的样式,则将记事本中的字体改为该样式
{
Font font = (Font) scene.getUserData();
ta.setFont(font);
}
}
// 状态栏
@FXML
void onStateMenu(ActionEvent event) {
if (StateMenu.isSelected())
label.setVisible(true);
else
label.setVisible(false);
}
}
package application;
import java.util.List;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextArea;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
public class TypefaceController {
//字体
@FXML
private ComboBox<String> Cb1;
//字形
@FXML
private ComboBox<String> Cb2;
//大小
@FXML
private ComboBox<String> Cb3;
@FXML
private Button SureBtn;
@FXML
private TextArea ta;
public Font Txtfont=Font.font(12);
private Font font=Font.font(12);
//字体
String type="宋体";
//字形
String special="常规";
//大小
int size=12;
@FXML
void initialize() {
List<String> getFontName = Font.getFontNames();
Cb1.getItems().addAll(getFontName);
Cb2.getItems().addAll("常规","粗体","斜体","粗斜体");
for(int i=2;i<=32;i=i+2)
{
Cb3.getItems().add(i+"");
}
Cb1.setOnAction(e->{
type = Cb1.getValue();
setFont();
});
Cb2.setOnAction(e->{
special = Cb2.getValue();
setFont();
});
Cb3.setOnAction(e->{
size = Integer.parseInt(Cb3.getValue());
setFont();
});
}
//设置字体样式
public void setFont() {
font = Font.font(type, size);
if(special.equals("常规"))
font=Font.font(type, FontWeight.NORMAL, FontPosture.REGULAR,size);//常规
else if(special.equals("粗体"))
font=Font.font(type, FontWeight.BOLD, FontPosture.REGULAR,size);//粗体
else if(special.equals("斜体"))
font=Font.font(type, FontWeight.NORMAL, FontPosture.ITALIC,size);//斜体
else if(special.equals("粗斜体"))
font=Font.font(type, FontWeight.BOLD, FontPosture.ITALIC,size);//粗斜体
ta.setFont(font);
}
@FXML
void onSureBtn(ActionEvent event) {
Txtfont=font;
Scene scene = SureBtn.getScene();
//把选定的字体记录到用户数据中
scene.setUserData(Txtfont);
//点击确定后,关闭窗口
Stage stage=(Stage)SureBtn.getScene().getWindow();
stage.close();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.web.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<BorderPane prefHeight="461.0" prefWidth="699.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.NoteBookController">
<center>
<TextArea fx:id="ta" prefHeight="434.0" prefWidth="658.0" BorderPane.alignment="CENTER" />
</center>
<top>
<MenuBar BorderPane.alignment="CENTER">
<menus>
<Menu mnemonicParsing="false" text="文件">
<items>
<MenuItem fx:id="NewMenu" mnemonicParsing="false" onAction="#onNewMenu" text="新建" />
<MenuItem fx:id="OpenMenu" mnemonicParsing="false" onAction="#onOpenMenu" text="打开" />
<MenuItem fx:id="SaveMenu" mnemonicParsing="false" onAction="#onSaveMenu" text="保存" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="编辑">
<items>
<MenuItem fx:id="FindMenu" mnemonicParsing="false" onAction="#onFindMenu" text="查找" />
<MenuItem fx:id="ReplaceMenu" mnemonicParsing="false" onAction="#onReplaceMenu" text="替换" />
<MenuItem fx:id="Undo" mnemonicParsing="false" onAction="#onUndoMenu" text="撤销" />
<MenuItem fx:id="Redo" mnemonicParsing="false" onAction="#onRedoMenu" text="重做" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="格式">
<items>
<CheckMenuItem fx:id="WrapMenu" mnemonicParsing="false" onAction="#onWrapMenu" text="自动换行" />
<MenuItem fx:id="TypefaceMenu" mnemonicParsing="false" onAction="#onTypefaceMenu" text="字体..." />
</items>
</Menu>
<Menu mnemonicParsing="false" text="查看">
<items>
<CheckMenuItem fx:id="StateMenu" mnemonicParsing="false" onAction="#onStateMenu" text="状态栏" />
</items>
</Menu>
</menus>
</MenuBar>
</top>
<bottom>
<Label fx:id="label" text="状态栏" BorderPane.alignment="CENTER" />
</bottom>
</BorderPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="222.0" prefWidth="448.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.TypefaceController">
<children>
<ComboBox fx:id="Cb1" layoutX="50.0" layoutY="16.0" prefHeight="30.0" prefWidth="96.0" promptText="字体" />
<ComboBox fx:id="Cb2" layoutX="182.0" layoutY="16.0" prefHeight="30.0" prefWidth="96.0" promptText="字形" />
<ComboBox fx:id="Cb3" layoutX="319.0" layoutY="16.0" prefHeight="30.0" prefWidth="88.0" promptText="大小" />
<TextArea fx:id="ta" editable="false" focusTraversable="false" layoutX="14.0" layoutY="91.0" mouseTransparent="true" prefHeight="116.0" prefWidth="264.0" text="个人记事本
AaBbYyZz" />
<Label layoutX="14.0" layoutY="61.0" text="示例" />
<Button fx:id="SureBtn" layoutX="328.0" layoutY="177.0" mnemonicParsing="false" onAction="#onSureBtn" prefHeight="30.0" prefWidth="71.0" text="确定" />
</children>
</AnchorPane>