前言
本人所写的图书管理系统,是基于mysql、数据库和用scenebuilder实现javafx的可视化界面所实现的,我的作品分类较多(如下图,上面方框中每个类都对应一个图形化界面),但只要搞清楚一个他们之间相似的对应操作,剩下的就是照葫芦画瓢一般的系统书写,对于提升我的逻辑思维能力是一次巨大的挑战。
全部具体代码可参考我的这篇文章:https://blog.csdn.net/Mrs_Lupin/article/details/135732021?spm=1001.2014.3001.5501
在这里仅做我认为重要部分的讲解,希望可以对你有所帮助!
一、ER图
二、系统结构图
三、页面跳转关系
四、页面跳转实现
以登陆成功跳入管理员页面为例。
总的来说就是通过这个页面的id获取这个页面的stage,再建立另一个页面的根节点,为新页面设置宽高和标题,关闭之前的页面之后,打开新的页面
//管理员登录按钮(此按钮绑定点击事件)
public void ma_login_bt() throws Exception {
//获取m_login此id所在页面
Stage previousStage = (Stage) m_login.getScene().getWindow();
if (ma()) {
//账号密码验证成功
//管理员状态下
state=1;
//得到um_id的内容
String umId=um_id.getText();
//将umId的值传给Manager对象并存储到静态变量loginManager中
//方便我们在另一个页面使用此对象信息
loginManager=new Manager(umId);
//关闭前一个窗口
previousStage.close();
//创建新的根节点(此页面链接写你所要跳转的那个页面)
Parent root = FXMLLoader.load(getClass().getResource("../view/maLogin.fxml"));
//创建新的舞台
Stage stage = new Stage();
//创建新的场景
Scene scene = new Scene(root, 790.0, 522.0);
//设置窗口不可改变大小
stage.setResizable(false);
//设置窗口标题
stage.setTitle("管理员操作");
//设置窗口视图
stage.setScene(scene);
//展现窗口
stage.show();
} else {
//验证失败,重新加载当前窗口
um_id.setText("");
um_psw.setText("");
//创建一个弹窗
Main.tips("数据输入有误或为空,请重新输入", previousStage);
}
}
//管理员登录
public boolean ma() throws Exception {
//输入不为空
if(um_id.getText()!=null&&um_psw.getText()!=null){
//管理员账号
String maId=um_id.getText();
//管理员密码
int maPsw;
try {
//将密码转为int型
maPsw = Integer.parseInt(um_psw.getText());
} catch (NumberFormatException e) {
// 输入的文本不是一个有效的整数,处理异常情况
return false;
}
//返回和数据库的比较结果
boolean maPass = managerOp.maLogin(maId,maPsw);
return maPass;
}else{
System.out.println("数据输入为空或者错误,请重新输入");
return false;
}
}
五、注册提示框
注册逻辑如下:当用户输入未注册过的账号登录时,将弹出注册提示框询问用户是想以此账号新注册一个账号,还是想取消返回
将registerNumber设为静态变量,用以监测整个页面中此窗口数
//注册提示框
public void registerTips(Stage stage){
// 窗口数+1
registerNumber++;
// 添加文本和按钮
Label l1 = new Label("检测到您输入的数据有误");
Label l2 = new Label("您是否想以当前输入的数据注册账号?");
Button b1 = new Button("确定");
Button b2 = new Button("取消");
// 创建面板
// 将文本和按钮加入盒子中
VBox vb = new VBox(l1,l2,b1,b2);
Scene sc = new Scene(vb, 400, 250);
//为文本和按钮设置位置和间隔
b1.setTranslateX(-100);
b1.setTranslateY(40);
b2.setTranslateX(100);
b2.setTranslateY(-15);
vb.setSpacing(30);
vb.setAlignment(Pos.CENTER);
Stage stage1 = new Stage();
stage1.setScene(sc);
// 设置窗口大小不可调节
stage1.setResizable(false);
//点击关闭按钮之后窗口数减一,即此窗口关闭
stage1.setOnCloseRequest(windowEvent -> {
registerNumber--;
});
//此按钮实现关闭此页面打开下一页面
b1.setOnAction(actionEvent -> {
registerNumber--;
stage1.close();
Parent root = null;
try {
//创建下一个页面的根节点
root = FXMLLoader.load(getClass().getResource("../view/userRegister.fxml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
stage.close();
//创建新的舞台
Stage stage3 = new Stage();
//创建新的场景
Scene scene = new Scene(root, 468.0, 584.0);
stage3.setResizable(false);
stage3.setTitle("用户注册");
stage3.setScene(scene);
stage3.show();
});
//点击取消按钮后关闭窗口
b2.setOnAction(actionEvent -> {
registerNumber--;
stage1.close();
});
//只能操作该窗口
stage1.initOwner(stage);
//设置窗口模态,使用户只能与此窗口交互,直到对话框被关闭。
stage1.initModality(Modality.WINDOW_MODAL);
//设置在窗口永远在最上层
stage1.setAlwaysOnTop(true);
stage1.setTitle("提示");
stage1.show();
}
六、初始化加载数据
在页面显示出来的时候就将对应的数据加载出来,如下图
初始化表格(下面的loadData()),在页面加载的时候就将查询到的数据插入
当FXML文件加载并关联控制器类时,JavaFX会自动创建控制器类的实例,
并自动调用initialize()方法。在initialize()方法内部,loadData()方法被调用,
从而实现在加载FXML文件时自动加载数据。
@FXML
//fxml的初始化方法
public void initialize() throws Exception{
loadData(Controller.loginUser.getId());
}
//初始化表格
public void loadData(String userId) throws Exception{
//查询语句,查询表中所有数据
String sql = "SELECT book_id,name,author,press,price,book_status,book_class,borrow_time FROM borrow_log,book where user_id=? AND borrow_log.book_id=book.id;";
//ResultSet接收查询结果
ResultSet rs = jdbc.executeQuery(sql,userId);
//声明一个集合
ObservableList<BrwLog> brwList = FXCollections.observableArrayList();
//循环ResultSet中的数据
while (rs.next()) {
//根据数据库中的属性存储数据到变量中(按列存储)
String bookId = rs.getString("book_id");
String bookName = rs.getString("name");
String author = rs.getString("author");
String press = rs.getString("press");
double price = rs.getDouble("price");
String bookStatus = null;
//改变存储内容
if(rs.getInt("book_status")==0){
bookStatus ="已归还";
}else if(rs.getInt("book_status")==1){
bookStatus="借阅中";
}
String bookClass = rs.getString("book_class");
String brwTime= rs.getString("borrow_time");
//创建实体类的一个对象,存储变量
BrwLog log = new BrwLog(bookId, bookName, author, press, price,bookStatus,bookClass,brwTime);
//每个对象为一行,将此行放入集合中
brwList.add(log);
}
//将实体类的属性与每一列对应设置
brw_id.setCellValueFactory(new PropertyValueFactory<>("bookId"));
brw_name.setCellValueFactory(new PropertyValueFactory<>("bookName"));
brw_au.setCellValueFactory(new PropertyValueFactory<>("author"));
brw_press.setCellValueFactory(new PropertyValueFactory<>("press"));
brw_price.setCellValueFactory(new PropertyValueFactory<>("price"));
brw_status.setCellValueFactory(new PropertyValueFactory<>("bookStatus"));
brw_class.setCellValueFactory(new PropertyValueFactory<>("bookClass"));
brw_wartime.setCellValueFactory(new PropertyValueFactory<>("borrowTime"));
//数据添加到表格中
book_brw_list.setItems(brwList);
}
七、数据录入限制
与第六点相同,将数据限制写进初始化方法中,使用户在输入信息同时被限制
//初始化限制数据输入
@FXML
public void initialize() throws Exception{
numLimit(um_id,6);
// 创建一个整数输入格式的 TextFormatter
TextFormatter<Integer> textFormatter = new TextFormatter<>(change -> {
String newText = change.getControlNewText();
// 仅允许输入数字且位数小于6
if ((newText.matches("\\d*"))&&(change.getControlNewText().length() <= 6)) {
return change;
} else {
return null; // 不接受非数字字符,不接受超过最大字符限制的输入
}
});
// 将 TextFormatter 应用到 TextArea
um_psw.setTextFormatter(textFormatter);
}
//检查字数是否合法
public static void numLimit(TextField textField,Integer num){
// 创建一个字符限制的文本过滤器
textField.setTextFormatter(new TextFormatter<String>(change -> {
if (change.getControlNewText().length() <= num) {
return change;
} else {
return null; // 不接受超过最大字符限制的输入
}
}));
}
八、选中修改并实现信息同步
首先用静态变量BookUpdate.oldStage2存储图书管理界面的stage,在此中获取选中行的信息,并存入到一个静态对象BookUpdate.selectRow中,当用户点击修改按钮后,这本书的信息会显示在修改页面,并保持可修改的状态,用户修改过后,点击确认按钮,获取新修改的信息存入数据库中,修改页面关闭同时重新加载原页面
//修改图书信息按钮
public Book book_update_bt() throws IOException {
//获取选中行
Book selectedBook= (Book) book_list.getSelectionModel().getSelectedItem();
//选中行不为空就打开界面
if(selectedBook!=null){
//获取选中行的所有信息
String oldId=selectedBook.getId();
String oldName=selectedBook.getName();
String oldAuthor=selectedBook.getAuthor();
String oldPress=selectedBook.getPress();
BigDecimal oldPrice=selectedBook.getPrice();
int oldNum=selectedBook.getNum();
String oldBookClass=selectedBook.getBookClass();
//将选中行的信息保存在静态变量selectRow中,这样在下一个页面中可以初始化加载要更改的信息
BookUpdate.selectRow=new Book(oldId,oldName,oldAuthor,oldPress,oldPrice,oldNum,oldBookClass);
//存储此页面的stage
BookUpdate.oldStage2 = (Stage) book_add.getScene().getWindow();
// 重新获取这个页面的根节点
FXMLLoader fxmlLoader = new FXMLLoader();
Parent root = fxmlLoader.load(BookMa.class.getResource("../view/book_update.fxml"));
Scene scene = new Scene(root, 532.0, 489.0);
Stage stage = new Stage();
stage.setResizable(false);
stage.setTitle("修改图书信息");
stage.setScene(scene);
stage.show();
return null;
}else{
//获取当前页面的stage并弹出提示框
Stage stage = (Stage) book_update.getScene().getWindow();
Main.tips("修改失败,请重新操作",stage);
return null;
}
}
//修改状态下的确认
public void update_con_bt(MouseEvent mouseEvent) throws IOException {
//在各个输入框信息都不为空的情况下
if(!update_name.getText().isEmpty()&&!update_author.getText().isEmpty()&&!update_press.getText().isEmpty()&&!update_author.getText().isEmpty()&&!update_price.getText().isEmpty()&&!update_num.getText().isEmpty()&&!update_type.getText().isEmpty()){
//存储各个输入框的信息
String newBookName=update_name.getText();
String newAuthor=update_author.getText();
String newPress=update_press.getText();
double newPrice= Double.parseDouble(update_price.getText());
int newBookNum= Integer.parseInt(update_num.getText());
String newBookType=update_type.getText();
//修改数据库中的数据
if(managerOp.bookUpdate(newBookName,newAuthor,newPress,newPrice,newBookNum,newBookType,selectRow.getId())){
//获取当前页面stage并关闭页面
Stage stage = (Stage) update_con.getScene().getWindow();
stage.close();
// 加载book_ma.fxml文件
Parent root = FXMLLoader.load(getClass().getResource("../view/book_ma.fxml"));
// 设置舞台的场景
BookUpdate.oldStage2.setScene(new Scene(root));
}
}else{
Stage stage = (Stage) update_con.getScene().getWindow();
Main.tips("录入图书信息不能为空", stage);
}
}
九、封装通用的更新和查询操作
如下操作中只需提供sql语句和与sql语句匹配的参数即可
public static boolean exeUpdate(String sql, Object... params) {
//获取连接对象
Connection conn = getConn();
PreparedStatement ps = null;
try {
//获取预编译对象
ps = conn.prepareStatement(sql);
//执行参数赋值操作
if (Objects.nonNull(params)) {
//循环将所有的参数赋值
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
//执行更新
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
close(null, ps, conn);
}
return false;
}
public static ResultSet executeQuery(String sql , Object... params) {
Connection conn = getConn();
PreparedStatement ps = null;
try {
//获取预编译对象
ps = conn.prepareStatement(sql);
//执行参数赋值操作
if (Objects.nonNull(params)) {
//循环将所有的参数赋值
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
//执行更新
return ps.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
close(null, ps, conn);
}
return null;
}
十、两个bug修改
如何将一个页面的数据传输给另一个页面?
我原本的想法是将fxml中的绑定的id属性设为静态的,进而在其他类中直接调取信息,但会爆出如下的线程错误,这bug即便我将想要传信息的两个类写在一个类中也无法解决。其解决方法是将要传输的信息存为静态变量
如何实现在当前页面修改数据,在另一个页面同步更新?
先静态存储页面1的stage,打开页面2后存储已经修改过的数据,并存储到数据库中,在页面2关闭的同时,从根节点加载页面1的scene,并放在中页面1的stage中,而页面1的重新加载初始化是从数据库赋值,相当于“画板”不变,改变的是“画布”