JavaFX入门笔记

背景

Java选修课第四次实验

所需工具

  • IDEA
  • JavaFX插件(需要Maven)
  • JavaFX Scene Builder

参考资料

开始操作

安装JavaFX插件

File -> Settings -> Plugins中搜索javafx然后下载第一个

之后便可以在IDEA中创建JavaFX项目

配置JavaFX Scene Builder

https://www.oracle.com/java/technologies/javafxscenebuilder-1x-archive-downloads.html 从网址下载适合自己电脑的版本

点击运行进行安装

点击下一步自行安装即可

安装完毕后电脑桌面会出现如图图标

创建项目


此处放上最终项目目录

设计界面

  1. 先对自动生成的.fxml文件和启动类改名,此处分别改为mainWindow和MainWindow
  2. 右键mainWindow.fxml并选择在SceneBuilder打开
  3. 设计界面
  4. 进行相关绑定
  • 在IDEA中打开mainWindow.fxml
  • 注意:给控件设置id时要写fx:id="",不能只写id="",否则将无法绑定!!!

编写DAO包中的类(实现文件读写操作)

CustomerDAO.java源码:

package banking.DAO;


import javafx.util.Pair;

import java.io.IOException;
import java.io.RandomAccessFile;

public class CustomerDAO {

    //Specify the size of two string fields in the record
    final static int FIRST_NAME_SIZE = 16;//FirstName最大长度(也是存储长度,不足则用空格不足)
    final static int LAST_NAME_SIZE = 16;//LastName最大长度
    public final static int CUSTOMER_SIZE = (FIRST_NAME_SIZE + LAST_NAME_SIZE);//每份数据的最大长度

    //写入用户数据
    public static void writeCustomr(String first, String last, RandomAccessFile raf) {
        try {
            raf.seek(raf.length());//在文件尾部进行写操作
            FixedLengthStringIO.writeFixedLengthString(
                    first, FIRST_NAME_SIZE, raf
            );//写入firstname
            FixedLengthStringIO.writeFixedLengthString(
                    last, LAST_NAME_SIZE, raf
            );//写入lastname
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //在指定位置写入用户数据
    public static void writeCustomer(String first, String last, RandomAccessFile raf, long position) {
        try {
            raf.seek(position);//前往特定位置进行写操作
            FixedLengthStringIO.writeFixedLengthString(
                    first, FIRST_NAME_SIZE, raf
            );//写入firstname
            FixedLengthStringIO.writeFixedLengthString(
                    last, LAST_NAME_SIZE, raf
            );//写入lastname
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //读出用户数据
    public static Pair readCustomer(long position, RandomAccessFile raf) throws IOException{
        raf.seek(position);//前往特定位置进行读操作

        String first = FixedLengthStringIO.readFixedLengthString(
                FIRST_NAME_SIZE, raf
        ); //读出firstname
        String last = FixedLengthStringIO.readFixedLengthString(
                LAST_NAME_SIZE, raf
        ); //读出lastname

        //返回一个记录姓名的键值对
        return new Pair<String, String>(first, last);
    }
}

编写控制类(实现各自操作)

  • Add:将用户输入的客户名添加进文件中并在List显示
  • Del:将用户输入的客户名在文件中删除并更新List;若输入的客户名不存在则提示删除失败
  • Search:查找用户输入的客户名,若存在则会在List中置顶;若不存在则提示查找失败
  • Sort:将现有数据按照姓名进行排序并在List更新
package banking.controller;

import java.io.IOException;
import java.io.RandomAccessFile;

import java.net.URL;
import java.util.*;

import banking.DAO.CustomerDAO;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.util.Pair;


public class CustomersController implements Initializable{
    //对以下几个成员变量进行@FXML注入,注意变量名与fxml文件中的fx:id对应相同
    //添加@FXML注解表明与fxml文件进行绑定
    @FXML
    private ListView customersList;
    @FXML
    private TextField firstName;
    @FXML
    private TextField lastName;

    // Access customers.dat using RandomAccessFile
    private RandomAccessFile raf;

    // A list which stores customers' name
    List<String>names = new ArrayList<>();

    //重写初始化函数,类似于构造函数
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        // Open or create a random access file
        try {
            raf = new RandomAccessFile("customers.dat", "rw");
        }
        catch(IOException ex) {
            System.out.print("Error: " + ex);
            System.exit(0);
        }

        //设置ListView不可编辑
        customersList.setEditable(false);

        //将文件中原先存有的数据读出并展示
        try {
            getCustomersFromRAF();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    //add事件
    //添加@FXML注解表明与fxml文件进行绑定
    @FXML
    protected void onAddButtonClick() throws IOException {
        //调用接口写入客户名
        CustomerDAO.writeCustomer(firstName.getText(), lastName.getText(), raf);
        getCustomersFromRAF();//读出数据并展示
    }

    //del事件
    //添加@FXML注解表明与fxml文件进行绑定
    @FXML
    protected void onDelButtonClick() throws IOException {
        //获取用户输入的客户名
        String target = clearSpaces(firstName.getText() + ',' + lastName.getText());
        if (names.contains(target)) {//若该客户名存在
            //重新读取文件内容
            long currentPosition = 0;//定位指针
            names.clear();//清空列表,准备重新读入
            while (currentPosition < raf.length()) {
                //读出客户名,存在Pair对象中
                Pair<String, String>customer = CustomerDAO.readCustomer(currentPosition, raf);
                //对读出的客户名进行拼接操作
                String name = clearSpaces(customer.getKey()) + ',' + clearSpaces(customer.getValue());
//            System.out.println(name);
                names.add(name);//在客户名列表中新增

                //自增(偏移量)
                //注意要2*CustomerDAO.CUSTOMER_SIZE
                //具体原因不清楚,但.dat文件中每条数据的长度为2 * CustomerDAO.CUSTOMER_SIZE
                //因此偏移量为2 * CustomerDAO.CUSTOMER_SIZE而不是CustomerDAO.CUSTOMER_SIZE
                currentPosition += 2 * CustomerDAO.CUSTOMER_SIZE;
            }

            //用最后一条数据覆盖要删除的数据
            String[]name = names.get(names.size()-1).split(",");//获取最后一条数据并存在字符串数组中
            //写入位置names.indexOf(target) * 2 * CustomerDAO.CUSTOMER_SIZE
            //解读:
            //names.indexOf(target)为要删除的数据在客户名列表中对应的索引,也为在文件中的索引
            //将此索引乘以偏移量,即为此条数据在文件中的具体位置,然后进行写入操作即可覆盖
            CustomerDAO.writeCustomer(name[0], name[1], raf,names.indexOf(target) * 2 * CustomerDAO.CUSTOMER_SIZE);
            raf.setLength(raf.length() - 2 * CustomerDAO.CUSTOMER_SIZE);//重置raf数据长度(删除最后一条数据)

            //重新展示
            getCustomersFromRAF();
        } else {
            //新建一个对话框提示删除失败
            Dialog<ButtonType> warning = new Dialog<>();
            warning.getDialogPane().getButtonTypes().add(new ButtonType("确认", ButtonBar.ButtonData.OK_DONE));
            warning.setTitle("删除失败");
            warning.setContentText("该客户不存在");
            warning.show();
        }

    }

    //search事件
    //添加@FXML注解表明与fxml文件进行绑定
    @FXML
    protected void onSearchButtonClick() {
        //查找该元素并交换位置
        String target = clearSpaces(firstName.getText() + ',' + lastName.getText());
        if (names.contains(target)) {//若要查找的客户名存在
            Collections.swap(names, 0, names.indexOf(target));//则将其置顶,即放在names中第一个元素的位置
            //将names的数据放入ListView中
            ObservableList<String>items = FXCollections.observableArrayList(names);
            customersList.setItems(items);
        } else {
            //新建一个对话框提示查找失败
            Dialog<ButtonType> warning = new Dialog<>();
            warning.getDialogPane().getButtonTypes().add(new ButtonType("确认", ButtonBar.ButtonData.OK_DONE));
            warning.setTitle("查找失败");
            warning.setContentText("该客户不存在");
            warning.show();
        }
    }

    //sort事件
    //添加@FXML注解表明与fxml文件进行绑定
    @FXML
    protected void onSortButtonClick() {
        //对names进行排序
        names.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                String[] name1 = o1.split(",");
                String[] name2 = o2.split(",");
                if (name1[1].equals(name2[1])) {
                    return name1[0].compareTo(name2[0]);
                } else {
                    return name1[1].compareTo(name2[1]);
                }
            }
        });
        //将排序后的names的数据放入ListView中
        ObservableList<String>items = FXCollections.observableArrayList(names);
        customersList.setItems(items);
    }

    //从文件中读取数据并存入names然后展示
    protected void getCustomersFromRAF() throws IOException {
        long currentPosition = 0;
        names.clear();
        while (currentPosition < raf.length()) {
            //读出客户名,存在Pair对象中
            Pair<String, String>customer = CustomerDAO.readCustomer(currentPosition, raf);
            //对读出的客户名进行拼接操作
            String name = clearSpaces(customer.getKey()) + ',' + clearSpaces(customer.getValue());
//            System.out.println(name);
            names.add(name);//在客户名列表中新增

            //自增(偏移量)
            //注意要2*CustomerDAO.CUSTOMER_SIZE
            //具体原因不清楚,但.dat文件中每条数据的长度为2 * CustomerDAO.CUSTOMER_SIZE
            //因此偏移量为2 * CustomerDAO.CUSTOMER_SIZE而不是CustomerDAO.CUSTOMER_SIZE
            currentPosition += 2 * CustomerDAO.CUSTOMER_SIZE;
        }
        //获取姓名
        ObservableList<String>items = FXCollections.observableArrayList(names);
        customersList.setItems(items);
    }

    //清空后缀空格
    public String clearSpaces(String s) {
        char[] chars = s.toCharArray();
        for (int i = s.length() - 1; i >= 0; i--) {
            if (chars[i] != ' ') {
                //从后往前找到第一个非空格的字符并截取
                return s.substring(0, i + 1);
            }
        }
        return "";//若原字符串全是空格/为空则返回空串
    }

}

配置启动类

在MainWindow.java中简单修改一下窗口尺寸和title即可运行

package banking.UI;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class MainWindow extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("mainWindow.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 577, 286);
        stage.setTitle("banking");
        stage.setScene(scene);
        stage.show();
    }

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

运行启动类

运行后效果如图: