JavaFX 与 Swing 的对比研究现代桌面 UI 的最佳选择

举报
柠檬味拥抱 发表于 2025/09/14 16:00:37 2025/09/14
【摘要】 本文面向已经会基础 Java 的读者,目标是用实战示例带你快速上手 JavaFX,掌握界面布局、事件处理、样式定制、多线程与打包部署等常用技巧。文章按章节展开,代码示例尽量完整,可直接复制运行。

JavaFX 与 Swing 的对比研究现代桌面 UI 的最佳选择

本文面向已经会基础 Java 的读者,目标是用实战示例带你快速上手 JavaFX,掌握界面布局、事件处理、样式定制、多线程与打包部署等常用技巧。文章按章节展开,代码示例尽量完整,可直接复制运行。
在这里插入图片描述

前言

为什么选择 JavaFX?

JavaFX 是 Oracle/开源社区维护的现代 Java 桌面 GUI 框架,支持响应式布局、CSS 样式、矢量图形、硬件加速和富媒体(音视频)等特性。相比 Swing,JavaFX 更现代、组件更丰富、易于使用 CSS 美化,且与 Java 生态兼容(Maven/Gradle)。
在这里插入图片描述

环境准备

Java 版本与 JavaFX 库

  • 推荐使用 Java 17 或更高 LTS。JavaFX 从 JDK 中独立出来,需要单独依赖(OpenJFX)。
  • 可以通过 Maven/Gradle 添加 org.openjfx:javafx 依赖,或使用 SDKMAN/手动下载 OpenJFX SDK 并配置运行参数。

Maven 快速配置(示例)

<!-- pom.xml 中关键片段(仅示意) -->
<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.source>17</maven.compiler.source>
  <maven.compiler.target>17</maven.compiler.target>
  <javafx.version>21.0.0</javafx.version> <!-- 请根据实际版本调整 -->
</properties>

<dependencies>
  <dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-controls</artifactId>
    <version>${javafx.version}</version>
  </dependency>
  <dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-fxml</artifactId>
    <version>${javafx.version}</version>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.openjfx</groupId>
      <artifactId>javafx-maven-plugin</artifactId>
      <version>0.0.8</version>
      <configuration>
        <mainClass>com.example.todo.MainApp</mainClass>
      </configuration>
    </plugin>
  </plugins>
</build>

第一个示例:To-Do 应用(完整可运行)

目标

构建一个简单的 To-Do 列表应用,功能:

  • 添加任务(文本)
  • 标记完成 / 删除
  • 使用 CSS 美化
  • 展示后台任务示例(模拟保存或加载)

我们使用纯代码(非 FXML)实现,便于快速理解控件与布局关系。

完整代码(MainApp.java)

package com.example.todo;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class MainApp extends Application {

    private final ObservableList<TodoItem> items = FXCollections.observableArrayList();

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("简易 To-Do");

        // 顶部:输入框 + 添加按钮
        TextField input = new TextField();
        input.setPromptText("输入任务,按 Enter 添加");
        input.setPrefWidth(300);
        Button addBtn = new Button("添加");
        HBox topBar = new HBox(10, input, addBtn);
        topBar.setPadding(new Insets(10));

        // 中间:ListView 显示任务
        ListView<TodoItem> listView = new ListView<>(items);
        listView.setCellFactory(lv -> new TodoCell());
        VBox center = new VBox(listView);
        center.setPadding(new Insets(0, 10, 10, 10));
        VBox.setVgrow(listView, Priority.ALWAYS);

        // 底部:状态栏 + 模拟加载按钮(演示后台任务)
        Label status = new Label("就绪");
        Button loadBtn = new Button("模拟加载(后台)");
        HBox bottomBar = new HBox(10, status, new Region(), loadBtn);
        HBox.setHgrow(bottomBar.getChildren().get(1), Priority.ALWAYS);
        bottomBar.setPadding(new Insets(10));

        BorderPane root = new BorderPane();
        root.setTop(topBar);
        root.setCenter(center);
        root.setBottom(bottomBar);

        Scene scene = new Scene(root, 500, 400);
        scene.getStylesheets().add(getClass().getResource("/styles.css").toExternalForm());

        // 事件:添加任务
        addBtn.setOnAction(e -> addTask(input));
        input.setOnKeyPressed(e -> {
            if (e.getCode() == KeyCode.ENTER) addTask(input);
        });

        // 模拟后台加载任务
        loadBtn.setOnAction(e -> {
            status.setText("正在加载...");
            Task<Void> loadTask = new Task<>() {
                @Override
                protected Void call() throws Exception {
                    Thread.sleep(2000); // 模拟耗时
                    Platform.runLater(() -> {
                        items.addAll(new TodoItem("示例任务 A"), new TodoItem("示例任务 B"));
                    });
                    return null;
                }

                @Override
                protected void succeeded() {
                    status.setText("加载完成");
                }

                @Override
                protected void failed() {
                    status.setText("加载失败");
                }
            };
            new Thread(loadTask, "loader-thread").start();
        });

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

    private void addTask(TextField input) {
        String text = input.getText().trim();
        if (!text.isEmpty()) {
            items.add(new TodoItem(text));
            input.clear();
        }
    }

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

TodoItem 与自定义单元格(TodoItem.java、TodoCell.java)

// TodoItem.java
package com.example.todo;

public class TodoItem {
    private final String text;
    private boolean done = false;

    public TodoItem(String text) { this.text = text; }
    public String getText() { return text; }
    public boolean isDone() { return done; }
    public void setDone(boolean done) { this.done = done; }
}
// TodoCell.java
package com.example.todo;

import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;

public class TodoCell extends ListCell<TodoItem> {
    private final CheckBox checkBox = new CheckBox();
    private final Label label = new Label();
    private final Button delBtn = new Button("删除");
    private final HBox container = new HBox(10, checkBox, label, new Separator(), delBtn);

    public TodoCell() {
        container.setPadding(new Insets(6));
        container.setStyle("-fx-alignment: center-left;");
        HBox.setHgrow(label, Priority.ALWAYS);
        delBtn.setOnAction(e -> {
            getListView().getItems().remove(getItem());
        });
        checkBox.setOnAction(e -> {
            if (getItem() != null) {
                getItem().setDone(checkBox.isSelected());
                updateItem(getItem(), false);
            }
        });
    }

    @Override
    protected void updateItem(TodoItem item, boolean empty) {
        super.updateItem(item, empty);
        if (empty || item == null) {
            setGraphic(null);
        } else {
            label.setText(item.getText());
            label.setStyle(item.isDone() ? "-fx-strikethrough: true;" : "-fx-strikethrough: false;");
            checkBox.setSelected(item.isDone());
            setGraphic(container);
        }
    }
}

CSS(styles.css)

.root {
    -fx-font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
}

.button {
    -fx-font-size: 13px;
    -fx-padding: 6 12 6 12;
}

.list-cell {
    -fx-border-color: transparent;
}

进阶主题

在这里插入图片描述

FXML 与 Scene Builder

  • FXML 是声明式 UI(类似 HTML),将视图和逻辑分离。使用 Scene Builder 可以可视化编辑 FXML。
  • 控制器类通过 @FXML 注入控件。
  • 推荐:大项目使用 FXML 管理复杂布局,小工具可用纯代码构造。

模块系统(module-info.java)

  • 如果使用模块化(JPMS),需要在 module-info.javarequires javafx.controls; requires javafx.fxml;opens 控制器包给 javafx.fxml
  • 对初学者,可先用非模块化方式(移除 module-info)以减少配置复杂度。

多线程与 UI 线程

  • JavaFX 的 UI 操作必须在 JavaFX Application Thread 上执行。
  • 长耗时操作应使用 Task<V> / Service<V> 或在后台线程运行并通过 Platform.runLater() 更新 UI。
  • 示例已展示后台模拟加载(Task + new Thread(task).start())。

响应式与绑定(Bindings)

  • JavaFX 提供属性(StringPropertyIntegerProperty 等)和绑定 API,方便实现 UI 与数据同步。

  • 示例:

    label.textProperty().bind(textField.textProperty());
    

样式与主题

  • JavaFX 支持 CSS(大部分 CSS 属性与网页类似,但使用 -fx- 前缀)。
  • 可加载多套样式表实现主题切换。

部署与打包

使用 jpackage 打包原生安装包

  • 推荐流程:

    1. 使用 JDK 的 jlinkjpackage(JDK 14+ 自带 jpackage)打包运行时镜像。
    2. 注意将 JavaFX 模块包含进去,或使用 OpenJFX 的 SDK。
  • 示例命令(示意):

    jpackage --name MyApp --input target/ --main-jar myapp.jar --main-class com.example.todo.MainApp
    
  • 也可以用 jlink 定制运行时,再用 jpackage 生成 installer。

常见问题

  • NoClassDefFoundError: javafx/application/Application:表示未正确添加 JavaFX 运行时或 VM 参数缺失。

  • 在命令行运行时,若使用非模块化方式需要加 VM 参数:

    --module-path /path/to/javafx-sdk/lib --add-modules javafx.controls,javafx.fxml
    

开发与调试技巧

使用 IDE 插件与 Scene Builder

  • IntelliJ IDEA / Eclipse 都支持 JavaFX 开发,Scene Builder 可与 FXML 配合使用。
  • 启用 VM options(运行配置)以调试模块/运行时问题。

单元测试

  • UI 逻辑可抽离到无 UI 的服务层,便于单测。
  • 对 UI 的端到端测试可以使用 TestFX 等库。

实践建议与最佳实践

项目结构建议

  • com.example.app

    • MainApp(启动)
    • controller(FXML 控制器)
    • view(FXML / 资源)
    • model(数据模型)
    • service(业务逻辑、IO、后台任务)

分离关注点

  • 将 UI 和业务逻辑分离,使用观察者(Property)或 MVVM 模式(JavaFX 很适合实现简单 MVVM)。

性能优化

  • 避免在 UI 线程做耗时操作。
  • 对大量数据使用虚拟化控件(如 ListView 本身是虚拟化的,TableView 也提供虚拟化)。
  • 对图形密集型应用尽量使用硬件加速(通常 JavaFX 会自动启用)。

示例扩展与练习

练习题

  1. 将 To-Do 应用扩展为可保存/加载到本地 JSON 文件。
  2. 使用 FXML + Controller 重写界面,并添加编辑任务功能(双击编辑)。
  3. 使用 Bindings 实现“剩余任务计数”自动显示。
  4. 使用 Service 定期同步(模拟远程备份)并在任务栏显示进度。

总结

在这里插入图片描述

本文从 JavaFX 的特点与优势 出发,逐步带你完成了一个 完整的 To-Do 桌面应用。通过实战示例,你掌握了:

  • 环境搭建:如何配置 JavaFX 依赖(Maven/Gradle),解决常见运行问题。
  • 界面构建:使用 StageScene、布局容器(BorderPaneHBoxVBox 等)组织 UI。
  • 事件处理:按钮点击、键盘事件、任务勾选/删除。
  • 自定义组件:通过 ListCell 实现可复用的任务显示逻辑。
  • 样式美化:利用 CSS 为 JavaFX 界面增添现代感。
  • 多线程处理:用 TaskPlatform.runLater 实现后台任务加载。
  • 部署方法:介绍了 jpackage 打包为原生安装包的流程。

JavaFX 不仅能让 Java 程序拥有现代化的 UI 界面,还支持 CSS、FXML、硬件加速与响应式绑定,适合快速开发 跨平台桌面应用

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。