还在为Java无法写客户端而烦恼吗?不要怕,大胆的拥抱JavaFX吧!保证让你从入门到放弃!

最简单的项目

来,让我们创建一个最简单的JavaFX项目吧

创建项目

  1. 官网下载JavaFX依赖

  2. 回到项目中,File-Project Structure...-libraries-加号-你JavaFX解压后的路径/bin下的所有jar包

创建启动类

package vip.linfeng.javafx01;

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        // 设置title
        stage.setTitle("林风测试");
        // 显示窗口
        stage.show();
    }
}

启动项目(配置参数)

然后,当你第一次启动时估计会报错:

错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序

不慌,咱们直接配置一下vm虚拟机的参数

点击项目-Edit Configurations...-Modify options-Add VM options-在VM options中配置下面参数(注意:路径要写你解压的路径)

--module-path "D:\lib\sdk\javafx-sdk-21.0.7\lib" --add-modules javafx.controls,javafx.fxml

最后ctrl+shift+F10完美运行

基本代码

当你准备好后,你就可以开始愉快的敲代码啦!

首先,写个最基本的标签、布局、场景、窗口

public class Main extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        // 创建标签
        Label label = new Label("你好呀");
        // 创建布局,并将标签扔到布局中
        BorderPane pane = new BorderPane(label);

        // 创建场景,并将布局扔到场景中,并设置宽高
        Scene scene = new Scene(pane, 300, 300);

        // 将场景设置到窗口中
        stage.setScene(scene);
        // 设置title
        stage.setTitle("林风测试");
        // 显示窗口
        stage.show();
    }
}

运行结果如下:

完美!

基础部分

我先撂下官方文档

这里不过多解释各种控件用法和方法事件,我感觉只需要看官方的文档就足够了

Application类执行顺序

通过main()执行Application的launch(String str)方法,当然launch(String str)方法不传入任何值也是可以执行的.launch(String str)方法会默认执行本类下的init()、start()、stop()方法。执行下面的main()方法后显示顺序为:

这是初始化方法->这是start()方法->这是stop()方法->这是main()方法。

举例

public class Main extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        System.out.println("这是start方法");
        // 设置title
        stage.setTitle("林风测试");
        // 显示窗口
        stage.show();
    }

    @Override
    public void init() throws Exception {
        super.init();
        System.out.println("这是init方法");
    }

    @Override
    public void stop() throws Exception {
        super.stop();
        System.out.println("这是stop方法");
    }
}

运行结果:

这是init方法
这是start方法
这是stop方法

按钮组件及其单击事件

public class Main extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {
        // 创建按钮组件
        Button button = new Button("有种你点我啊");
        // 创建布局并将按钮扔进去
        BorderPane pane = new BorderPane(button);
        // 设置按钮单击事件
        button.setOnAction(e -> {
            getHostServices().showDocument("http://blog.linfeng.vip");
        });
        // 创建场景并把布局扔进去,并设置宽高
        Scene scene = new Scene(pane, 300, 300);
        // 将场景扔到舞台
        stage.setScene(scene);
        // 设置标题
        stage.setTitle("林风最帅");
        // 显示窗口
        stage.show();
    }
}

Stage类

  • Title:设置title标题文本

  • icon:设置窗口图标

  • resiziable:窗口是否可以改变大小

  • x,y,width,height:设置坐标和宽高,不常用

  • StageStyle:窗口风格样式

  • Modality:是否为模态窗口

  • event:窗口事件,关闭、最大化、最小化等等,不常用

案例

    @Override
    public void start(Stage stage) throws Exception {
        Button button0 = new Button("窗口0");
        Button button1 = new Button("窗口1");
        // 设置按钮位置
        button0.setLayoutX(200);
        button0.setLayoutY(200);
        button1.setLayoutX(200);
        button1.setLayoutY(250);
        // 创建锚定容器
        AnchorPane pane = new AnchorPane();
        pane.getChildren().addAll(button0, button1);

        button0.setOnAction(e -> {
            Stage stage0 = new Stage();
            stage0.setHeight(200);
            stage0.setWidth(300);
            // APPLICATION_MODAL该应用只能有这一个模态框,只能在该模态框操作,其它任何窗口都无法操作
//            stage1.initModality(Modality.APPLICATION_MODAL);
            // 设置父窗口
            stage0.initOwner(stage);
            // WINDOW_MODAL表示,只有父窗口是禁用的,其它窗口都可以使用
            stage0.initModality(Modality.WINDOW_MODAL);
            stage0.show();
        });

        button1.setOnAction(e -> {
            Stage stage1 = new Stage();
            stage1.setHeight(200);
            stage1.setWidth(300);
            stage1.show();
        });

        Scene scene = new Scene(pane, 500, 500);
        stage.setScene(scene);
        // 设置标题
        stage.setTitle("林风最帅");
        // 设置窗口图标,从窗口中获取Icons然后添加一个Image实例即可
        stage.getIcons().add(new Image("image/logo.png"));
        // 锁定窗口大小,禁止用户改变窗口大小,包括最大化,默认允许
        stage.setResizable(false);
        // 设置窗口风格样式
        // 注意:UNDECORATED在没有场景的情况下什么都不显示
        stage.initStyle(StageStyle.UNDECORATED);
        // 显示窗口
        stage.show();
    }

图片路径:

StageStyle和Modality

StageStyle风格

舞台具有以下样式之一:

  • StageStyle.DECORATED- 具有纯白色背景和平台装饰的舞台。(默认)

  • StageStyle.UNDECORATED- 具有纯白色背景且没有任何装饰的舞台。

  • StageStyle.TRANSPARENT- 背景透明且无任何装饰的舞台。

  • StageStyle.UTILITY- 具有纯白色背景和极简平台装饰的舞台。

Modality

  • Modality.APPLICATION_MODAL:定义一个模式窗口,阻止事件传递到任何其他应用程序窗口。

  • Modality.NONE:定义一个非模态且不阻塞任何其他窗口的顶级窗口。(默认)

  • Modality.WINDOW_MODAL:定义一个模式窗口,阻止事件传递到其整个所有者窗口层次结构。

Event

窗口事件,为减少代码看着不那么乱,单独拎出来了

    @Override
    public void start(Stage stage) throws Exception {
        Button button = new Button("窗口");
        button.setLayoutX(200);
        button.setLayoutY(200);
        AnchorPane pane = new AnchorPane();
        pane.getChildren().addAll(button);

        // 关闭操作系统默认退出程序操作
        Platform.setImplicitExit(false);
        stage.setOnCloseRequest(e -> {
            // 关闭操作系统默认关闭窗口操作
            e.consume();

            // 弹出窗口询问是否关闭窗口
            // 若是则退出,不是则无操作
            Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
            alert.setTitle("退出程序");
            alert.setHeaderText(null);
            alert.setContentText("您是否要退出程序?");
            Optional<ButtonType> result = alert.showAndWait();
            if(result.get() == ButtonType.OK) {
                // 退出程序并关闭窗口
                Platform.exit();
                // 下面的代码仅仅是关闭窗口,并没有结束进程
                // stage.close();
            }
        });

        Scene scene = new Scene(pane, 500, 500);
        stage.setScene(scene);
        stage.setTitle("林风最帅");
        stage.show();
    }

页面场景切换及其鼠标图片样式

示例代码:

    @Override
    public void start(Stage stage) throws Exception {
        // 编写第一个场景
        Button button0 = new Button("hello world!");
        button0.setLayoutX(200);
        button0.setLayoutY(200);
        AnchorPane root = new AnchorPane();
        root.getChildren().addAll(button0);
        Scene scene = new Scene(root, 500, 500);

        // 给第一个场景设置鼠标图片,用来替换原鼠标样式
        scene.setCursor(new ImageCursor(new Image(getClass().getResourceAsStream("/image/cursor1.png"))));

        // 编写第二个场景
        Label label = new Label("你好,世界!");
        label.setLayoutX(200);
        label.setLayoutY(200);
        Button button1 = new Button("返回原参数页面");
        button1.setLayoutX(200);
        button1.setLayoutY(250);
        AnchorPane root1 = new AnchorPane();
        root1.getChildren().addAll(label, button1);
        Scene scene1 = new Scene(root1, 500, 500);

        // 给第二个场景设置鼠标图片,用来替换原鼠标样式
        scene1.setCursor(new ImageCursor(new Image(getClass().getResourceAsStream("/image/cursor.png"))));

        // 页面切换逻辑
        // 其实就是把场景进行替换
        button0.setOnAction(e -> {
            stage.setScene(scene1);
        });
        button1.setOnAction(e -> {
            stage.setScene(scene);
        });

        stage.setScene(scene);
        stage.setTitle("林风测试");
        stage.getIcons().addAll(new Image("image/logo.png"));
        stage.show();
    }

Node类

Node类是一个抽象类,其所有控件或其父类都继承的Node类

  • layoutX/layoutY/preWidth/preHeight

  • style/visible/opacity/blendMode

  • tanslateX/tanslateY/rotate/scaleX/scaleY/scaleZ

  • parent/scene/id

案例一:layoutX/layoutY/preWidth/preHeight、style

    @Override
    public void start(Stage stage) throws Exception {
        Label label = new Label("Hello world");

        // 设置坐标
        label.setLayoutX(200);
        label.setLayoutY(200);
        // 设置样式: 跟css一样,红色背景,蓝色边框,边框宽度3像素
        label.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 3px;");
        // 设置宽高
        label.setPrefWidth(200);
        label.setPrefHeight(80);
        // 设置label的内容居中
        label.setAlignment(Pos.CENTER);

        AnchorPane root = new AnchorPane();
        root.getChildren().addAll(label);
        Scene scene = new Scene(root, 500, 500);
        stage.setScene(scene);
        stage.show();
    }

案例二:visible/opacity/rotate/translateX/translateY

    @Override
    public void start(Stage stage) throws Exception {
        Label label = new Label("Hello world");

        // 设置样式
        label.setPrefWidth(200);
        label.setPrefHeight(200);
        label.setAlignment(Pos.CENTER);
        label.setStyle("-fx-background-color: red;");

        // 设置透明度
        label.setOpacity(0.5);
        // 旋转45度
        label.setRotate(45);
        // 平移
        label.setTranslateX(100);
        label.setTranslateY(50);

        // 不显示控件
        label.setVisible(false);

        AnchorPane root = new AnchorPane();
        root.getChildren().addAll(label);
        Scene scene = new Scene(root, 500, 500);
        stage.setScene(scene);
        stage.show();
    }

UI控件的属性绑定和属性监听

Property接口:在Node中几乎所有的属性都使用的Property子类的实例

案例:让一个圆形居中,并监听Y轴变化输出

    @Override
    public void start(Stage stage) throws Exception {
        Circle circle = new Circle();
        circle.setCenterX(250);
        circle.setCenterY(250);
        circle.setRadius(100);
        circle.setStroke(Color.BLACK);
        circle.setFill(Color.WHITE);
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);

        // 创建单项绑定
        circle.centerXProperty().bind(scene.widthProperty().divide(2));
        circle.centerYProperty().bind(scene.heightProperty().divide(2));

        // 监听器,当Y轴改变时触发
        circle.centerYProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
                System.out.println("Y轴改变,原来值为:" + oldValue + " 现在值为:" + newValue);
            }
        });

        root.getChildren().addAll(circle);

        stage.setScene(scene);
        stage.show();
    }

JavaFX中的事件驱动编程

Node抽象类中有很多事件

案例一:点击事件和键盘事件

    @Override
    public void start(Stage stage) throws Exception {
        AnchorPane root = new AnchorPane();

        Label label = new Label("你好");
        label.setLayoutX(200);
        label.setLayoutY(200);
        Button button = new Button("向上移动");
        button.setLayoutX(200);
        button.setLayoutY(250);

        // 设置按钮点击事件
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                label.setLayoutY(label.getLayoutY() - 5);
            }
        });

        root.getChildren().addAll(label, button);
        Scene scene = new Scene(root, 500, 500);

        // 设置键盘抬起的时候
        scene.setOnKeyReleased(e -> {
            KeyCode keyCode = e.getCode();
            if(keyCode.equals(KeyCode.DOWN)){
                System.out.println("aaaa");
                // 按下了向下的箭头
                label.setLayoutY(label.getLayoutY() + 5);
            }
        });

        stage.setScene(scene);
        stage.show();
    }

案例二:文件拖拽事件

    @Override
    public void start(Stage stage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);

        TextField textField = new TextField();
        textField.setLayoutX(150);
        textField.setLayoutY(200);

        // 定义一个当拖动手势在此范围内进行时要调用的函数Node。
        textField.setOnDragOver(e -> {
            // 传输模式:any表示包含所有传输模式的数组。
            e.acceptTransferModes(TransferMode.ANY);
        });

        // 松开手之后
        textField.setOnDragDropped(e -> {
            Dragboard dragboard = e.getDragboard();
            // 如果有文件
            if(dragboard.hasFiles()){
                // 获取文件路径
                String path = dragboard.getFiles().get(0).getAbsolutePath();
                textField.setText(path);
            }
        });

        root.getChildren().addAll(textField);
        stage.setScene(scene);
        stage.show();
    }

Color、Font和Image

Color

  • Color.颜色

  • Color.rgb(red, green, blue, alpha)

  • Color.hsb(a,b,c,d):色相饱和度明度不透明度

  • Color.web("#ffffff"):参考css

案例

    @Override
    public void start(Stage stage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);

        Circle circle = new Circle();
        circle.setCenterX(250);
        circle.setCenterY(250);
        circle.setRadius(100);


        // 设置填充色:红色,不透明度0.5
        circle.setFill(Color.web("#f00", 0.5));
        // 设置边框颜色:蓝色,不透明度0.5
        circle.setStrokeWidth(10);
        circle.setStroke(Color.rgb(0, 0, 255, 0.5));

        root.getChildren().addAll(circle);
        stage.setScene(scene);
        stage.show();
    }

Font

font(double size)
font(String family)
font(String family, double size)
font(String family, FontPosture posture, double size)
font(String family, FontWeight weight, double size)
font(String family, FontWeight weight, FontPosture posture, double size)

案例:

// 设置字体大小
label.setFont(new Font(30));

Image

用法:

Image(InputStream is)
Image(String url)
Image(String url, boolean backgroundLoading)

案例:

    @Override
    public void start(Stage stage) throws Exception {
        AnchorPane root = new AnchorPane();
        Scene scene = new Scene(root, 500, 500);
        ImageView imageView = new ImageView();
        Image image = new Image(getClass().getResourceAsStream("/image/logo.png"));
        imageView.setImage(image);
        root.getChildren().addAll(imageView);
        stage.setScene(scene);
        stage.show();
    }

恭喜你,到这里基础的部分就算完结了,你现在可以写一些简单的小软件了

进阶部分

FXML布局文件的使用

先看下纯Java的代码:

    @Override
    public void start(Stage stage) throws Exception {
        Label label = new Label("Hello world");
        label.setLayoutX(150);
        label.setLayoutY(200);
        label.setFont(new Font(30));
        Button button = new Button("向上移动");
        button.setLayoutX(150);
        button.setLayoutY(260);
        button.setOnAction(e -> label.setLayoutY(label.getLayoutY() - 5));
        AnchorPane root = new AnchorPane();
        root.getChildren().addAll(label, button);
        Scene scene = new Scene(root, 500, 500);
        stage.setScene(scene);
        stage.show();
    }

我们现在在项目的Java文件路径下创建一个FXML文件:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<?import javafx.scene.text.Font?>
<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="vip.linfeng.javafx01.DemoController"
            prefHeight="400.0" prefWidth="600.0">

    <children>
        <Label fx:id="la"
               text="Hello world"
               layoutX="150"
               layoutY="200">
            <font>
                <Font size="30" />
            </font>
        </Label>

        <Button fx:id="bu"
                onAction="#onUp"
                text="向上移动"
                layoutX="150"
                layoutY="260" />
    </children>

</AnchorPane>

然后创建一个controller文件

public class DemoController {
    @FXML
    Label la;

    @FXML
    Button bu;

    public void onUp() {
        la.setLayoutY(la.getLayoutY() - 5);
    }
}

最后简化主类

public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        Pane root = FXMLLoader.load(getClass().getResource("Demo.fxml"));
        Scene scene = new Scene(root, 500, 500);
        stage.setScene(scene);
        stage.show();
    }
}

运行一下,完美~

最后附上结构图:

知识补充

其实到上面就已经可以了,你现在可以写一些中小型项目,只要你会看文档就行,官方的文档是个好东西,会用会查那么80%的难题都不在话下。然后有一些比较常用的知识补充一下:

initialize方法

public class OneController {
    @FXML
    Button btn;

    @FXML
    void action(ActionEvent e) {
        System.out.println("行,你有种");
    }

    /**
     * 初始化事件
     * 可以用来绑定一些复杂的操作或做一些复杂的操作
     */
    public void initialize() {
        
    }
}

Maven项目

是不是感觉找各种依赖包特别麻烦?尤其当你好不容易把所有依赖包一个个下载完后,一运行发现,版本冲突,天塌了!

所以这次教你将JavaFX用Maven进行管理

这里我就不再重建项目了,偷个小懒,把之前项目的pom.xml端上来吧

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>site.hsdj</groupId>
    <artifactId>accessexcel</artifactId>
    <version>1.0</version>

    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!-- JavaFX版本 -->
        <javafx-version>21.0.7</javafx-version>
        <!-- ucanaccess版本 -->
        <u-can-access-version>5.0.1</u-can-access-version>
        <!-- dbutils版本 -->
        <commons-dbutils-version>1.7</commons-dbutils-version>
        <!-- POI版本 -->
        <poi-version>5.2.3</poi-version>
        <!-- log4j2版本 -->
        <log4j2-version>2.20.0</log4j2-version>
        <!-- commons-lang3版本 -->
        <commons-lang3-version>3.12.0</commons-lang3-version>
        <!-- lombok版本 -->
        <lombok-version>1.18.30</lombok-version>
        <!-- 请求依赖版本 -->
        <apache-httpcomponents>4.5.13</apache-httpcomponents>
        <!-- jackson版本 -->
        <jackson-version>2.18.2</jackson-version>
        <!-- yaml解析器版本 -->
        <snakeyaml-version>2.4</snakeyaml-version>
        <snakeyaml-engine-version>2.9</snakeyaml-engine-version>
    </properties>

    <dependencies>

        <!-- JavaFX依赖 -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx-version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-graphics</artifactId>
            <version>${javafx-version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-base</artifactId>
            <version>${javafx-version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>${javafx-version}</version>
        </dependency>

        <!-- access依赖 -->
        <dependency>
            <groupId>net.sf.ucanaccess</groupId>
            <artifactId>ucanaccess</artifactId>
            <version>${u-can-access-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>${commons-dbutils-version}</version>
        </dependency>

        <!-- POI依赖 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>${poi-version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>${poi-version}</version>
        </dependency>

        <!-- log4j2核心依赖,用于关闭报错,满足POI依赖 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j2-version}</version>
        </dependency>

        <!-- log4j2api依赖,用于可以正常使用log4j2 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j2-version}</version>
        </dependency>

        <!-- 常用工具包 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3-version}</version>
        </dependency>

        <!-- Lombok依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok-version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- 请求依赖 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>${apache-httpcomponents}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>${apache-httpcomponents}</version>
        </dependency>

        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson-version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson-version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson-version}</version>
        </dependency>

        <!-- yaml解析器 -->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>${snakeyaml-version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- 指定主类 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>site.hsdj.accessexcel.ApplicationMain</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

            <!-- 包含所有包 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>site.hsdj.accessexcel.ApplicationMain</mainClass>
                                </transformer>
                            </transformers>

                            <!-- 排除JavaFX相关依赖 -->
                            <filters>
                                <filter>
                                    <artifact>org.openjfx:*</artifact>
                                    <excludes>
                                        <exclude>**</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- 编译Java源代码 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <excludes>
                        <exclude>module-info.java</exclude>
                    </excludes>
                </configuration>
            </plugin>

        <resources>
            <resource>
                <!--把src/main/java目录下的properties、xml文件打包打进程序中-->
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    <include>**/*.fxml</include>
                </includes>
                <filtering>false</filtering>
            </resource>

            <resource>
                <!--把src/main/resources目录下的properties、xml、css、图片文件打包打进程序中-->
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.yml</include>
                    <include>**/*.xml</include>
                    <include>**/*.fxml</include>
                    <include>**/*.css</include>
                    <include>**/*.setting</include>
                    <!-- 添加图片格式支持 -->
                    <include>**/*.png</include>
                    <include>**/*.jpg</include>
                    <include>**/*.jpeg</include>
                    <include>**/*.gif</include>
                    <include>**/*.bmp</include>
                    <include>**/*.svg</include>
                </includes>
                <filtering>false</filtering>
            </resource>

            <resource>
                <!--把lib/目录下第三方jar包打进程序中,如systemPath目录下的jar-->
                <directory>lib/</directory>
                <includes>
                    <include>**/*.jar</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>

    </build>

</project>

别看依赖这么多,这里只需要关注JavaFX部分和打包插件即可

工具封装

AccessDBUtils

这里肯定会有人问了,唉~,MySQL呢?为啥不把MySQL端上来,而是Access?

因为我只封装过Access的,MySQL因为没在JavaFX项目中用过,所以。。。。

package site.hsdj.accessexcel.common.utils;

/**
 * @auth linfeng
 * @create 2025/6/4 10:40
 * @Description
 */
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * Access数据库操作工具类
 */
public class AccessDBUtils {
    private String dbPath;
    private String password;
    private Connection connection;

    /**
     * 构造函数
     * @param dbPath Access数据库文件路径
     * @param password 数据库密码(可为空)
     */
    public AccessDBUtils(String dbPath, String password) {
        this.dbPath = dbPath;
        this.password = password;
    }

    /**
     * 获取数据库连接
     * @return Connection对象
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public Connection getConnection() throws SQLException, ClassNotFoundException {
        if (connection == null || connection.isClosed()) {
            // 加载驱动
            Class.forName("net.ucanaccess.jdbc.UcanaccessDriver");

            // 构建连接字符串
            String connectionString = "jdbc:ucanaccess://" + dbPath;
            if (password != null && !password.isEmpty()) {
                connectionString += ";password=" + password;
            }

            // 建立连接
            connection = DriverManager.getConnection(connectionString);
        }
        return connection;
    }

    /**
     * 关闭数据库连接
     */
    public void closeConnection() {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                System.err.println("关闭数据库连接时出错");
                e.printStackTrace();
            }
        }
    }

    /**
     * 查询数据并映射为Bean列表
     * @param sql SQL查询语句
     * @param beanClass Bean类
     * @param <T> 泛型类型
     * @return Bean列表
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public <T> List<T> queryBeanList(String sql, Class<T> beanClass) throws SQLException, ClassNotFoundException {
        QueryRunner qr = new QueryRunner();
        return qr.query(getConnection(), sql, new BeanListHandler<>(beanClass));
    }

    /**
     * 查询数据并映射为Map列表
     * @param sql SQL查询语句
     * @return Map列表
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public List<Map<String, Object>> queryMapList(String sql) throws SQLException, ClassNotFoundException {
        QueryRunner qr = new QueryRunner();
        return qr.query(getConnection(), sql, new MapListHandler());
    }

    /**
     * 查询单个值
     * @param sql SQL查询语句
     * @return 查询结果
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public <T> T queryScalar(String sql) throws SQLException, ClassNotFoundException {
        QueryRunner qr = new QueryRunner();
        return qr.query(getConnection(), sql, new ScalarHandler<T>());
    }

    /**
     * 执行更新操作(INSERT, UPDATE, DELETE)
     * @param sql SQL语句
     * @param params 参数
     * @return 影响的行数
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public int update(String sql, Object... params) throws SQLException, ClassNotFoundException {
        QueryRunner qr = new QueryRunner();
        return qr.update(getConnection(), sql, params);
    }

    /**
     * 在try-with-resources中使用
     * @return 自动关闭的AccessDbUtil实例
     */
    public AutoCloseableAccessDbUtil autoCloseable() {
        return new AutoCloseableAccessDbUtil(this);
    }

    /**
     * 自动关闭的包装类
     */
    public static class AutoCloseableAccessDbUtil implements AutoCloseable {
        private final AccessDBUtils dbUtil;

        public AutoCloseableAccessDbUtil(AccessDBUtils dbUtil) {
            this.dbUtil = dbUtil;
        }

        public AccessDBUtils getDbUtil() {
            return dbUtil;
        }

        @Override
        public void close() {
            dbUtil.closeConnection();
        }
    }
}

运行案例:

public class AccessDBUtilsTest {
    public static void main(String[] args) {
        new AccessDBUtilsTest().testTryWithResources();
    }

    public void testTryWithResources() {
        // 或者使用try-with-resources方式
        try (AccessDBUtils.AutoCloseableAccessDbUtil autoDbUtil =
                     new AccessDBUtils("D:\\test\\db\\test.mdb", "test").autoCloseable()) {

            AccessDBUtils dbUtil = autoDbUtil.getDbUtil();
            // 执行数据库操作...
            // 查询示例2:查询Map列表
            List<Map<String, Object>> mapResults = dbUtil.queryMapList("select * from tablename where ID = 397949");
            for (Map<String, Object> row : mapResults) {
                System.out.println(row);
            }
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public void testTry() {
        AccessDBUtils dbUtil = new AccessDBUtils("D:\\test\\db\\test.mdb", "test");

        try {
            // 查询示例1:查询Bean列表
            List<ZuZhuangTest> results = dbUtil.queryBeanList(
                    "select ID as id, username as code from tablename where ID = 397946",
                    ZuZhuangTest.class);

            for (ZuZhuangTest item : results) {
                System.out.println("item = " + item);
            }

            // 查询示例2:查询Map列表
            List<Map<String, Object>> mapResults = dbUtil.queryMapList("select * from tablename where ID = 397949");
            for (Map<String, Object> row : mapResults) {
                System.out.println(row);
            }

            // 更新示例
//            int updatedRows = dbUtil.update("update tablename set code = ? where id = ?", "newCode", 1);
//            System.out.println("更新了" + updatedRows + "行");

        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 关闭连接
            dbUtil.closeConnection();
        }
    }
}

其实还有很多封装的工具这里因为篇幅问题,怕文章太长导致页面加载慢或卡顿,所以不再列举了

常见问题

接下来就是大家很容易遇到的一些问题(不知道大家遇到没,反正我遇到了)

纯Java项目设置resources目录

  1. 首先在项目根目录下创建一个文件夹,名称为resources

  2. File-Project Structure...

  3. 点击Modules

  4. 点击你创建的resources目录

  5. 点击Mark as 中的Resources即可设置成功

  6. 点击Apply,点击OK

设置右键FXML文件打开SceneBuilder

  1. 确保已安装 SceneBuilder

  2. 在 IntelliJ IDEA 中配置

    文件(File) > 设置(Settings) > 语言和框架(Languages & Frameworks) > JavaFX 2. 在 "SceneBuilder 路径" 中指定你的 SceneBuilder 可执行文件路径 3. 应用设置后,右键点击 FXML 文件即可看到 "Open in SceneBuilder" 选项

如图:

image-20250609093211513.png

路径问题

纯Java项目图片资源路径问题

我的目录结构如图:

生产环境使用(推荐使用)

// 给场景设置鼠标图片,用来替换原鼠标样式
scene1.setCursor(new ImageCursor(new Image(getClass().getResourceAsStream("/image/cursor.png"))));
// 设置窗口图标
stage.getIcons().add(new Image(getClass().getResourceAsStream("/image/logo.png")));

调试测试时使用(不推荐使用)

// 给场景设置鼠标图片,用来替换原鼠标样式
scene1.setCursor(new ImageCursor(new Image("file:resources/image/cursor.png")));
// 设置窗口图标
stage.getIcons().add(new Image("image/logo.png"));

Maven项目

以下案例全都是在你配置了maven之后的路径

         <resource>
             <!--把src/main/java目录下的properties、xml文件打包打进程序中-->
             <directory>src/main/java</directory>
             <includes>
                 <include>**/*.properties</include>
                 <include>**/*.xml</include>
                 <include>**/*.fxml</include>
             </includes>
             <filtering>false</filtering>
         </resource>
加载FXML文件
Pane root = FXMLLoader.load(getClass().getResource("/vip/linfeng/test/modules/home/fxml/DataBaseHome.fxml"));
Scene scene = new Scene(root);

你可以封装个工具类来简化代码:

public class FilePathUtils {
    /**
     * 获取FXML的绝对路径
     * @param module 模块路径
     * @param fxmlName FXML文件名称
     * @return 返回FXML文件的绝对路径
     */
    public static String getFXMLPath(String module, String fxmlName) {
        return String.format("/site/hsdj/accessexcel/modules/%s/fxml/%s.fxml", module, fxmlName);
    }
}

使用:

Pane root = FXMLLoader.load(getClass().getResource(FilePathUtils.getFXMLPath("home", "DataBaseHome")));