Fork me on GitHub

桌面应用JavaFx

官网

https://openjfx.cn/

JavaFX 是什么?

JavaFX 是 Java 平台上的下一代 GUI 工具包,最初由 Sun Microsystems 推出,后来归于 Oracle。它的设计理念与 Swing 完全不同,更偏向现代 UI 开发:

•支持 CSS 样式化
•FXML 结构化布局
•内置动画和媒体支持
•更易于绑定与响应式编程

与 Swing 相比,JavaFX 组件更美观、灵活性更强,同时更适合使用 MVVM 模式进行架构设计。

OpenJFX 与 JavaFX 的关系

JavaFX 在 Java 8 中曾是 JDK 的一部分。但从 JDK 11 开始,Oracle 将其剥离出了 JDK,转为一个独立的开源项目:OpenJFX。

名称 说明
JavaFX Oracle 的 GUI 工具包
OpenJFX JavaFX 的开源实现版本

目前主流的 JavaFX 开发,基本上都基于 OpenJFX,比如:

•Gluon 提供的 OpenJFX 二进制发布
•Scene Builder 等工具的支持

JavaFX 的核心组成

JavaFX 的体系庞大但清晰,可分为以下模块:
•javafx-base:基本类,如属性绑定
•javafx-controls:UI 控件,如 Button、TableView
•javafx-graphics:场景图、Canvas、绘图等
•javafx-fxml:FXML 解析器
•javafx-media:音视频播放支持
•javafx-web:WebView,嵌入浏览器

此外还有社区维护的扩展库,如:
•ControlsFX:增强 UI 控件
•Flowless:高性能虚拟滚动
•MVVMFX、DataFX:MVVM 框架支持

使用场景

JavaFX 并不是“老掉牙”的选择,它在以下场景中仍然大有可为:
•企业内网应用(无需浏览器)
•数据可视化与监控面板
•工控/医疗设备界面
•教育培训软件
•跨平台桌面客户端

MVVM 架构(推荐)

推荐使用 JDK 17+ 搭配 OpenJFX,同时使用 Maven 或 Gradle 来进行依赖管理。

项目结构

student-http-mvvm/
├── model/
│ └── Student.java
├── service/
│ └── StudentService.java <-- 调用 HTTP 接口
├── view/
│ └── StudentView.fxml
├── viewmodel/
│ └── StudentViewModel.java <-- 绑定属性 + 调用接口
├── controller/
│ └── StudentController.java <-- UI事件 → ViewModel
└── Main.java

mvvm对应类
image
类解释
image

调用关系流程

StudentView.fxml (View)
     │
     └── 绑定 fx:controller → StudentController.java (Controller)
             │
             └── UI事件转发 → StudentViewModel.java (ViewModel)
                     │
                     └── 调用 → StudentService.java (Service)
                             │
                             └── 请求/响应 → 后端 HTTP 接口
                                     │
                                     └── JSON 映射 → Student.java (Model)

代码

Maven 依赖(hutool + javafx)

<dependencies>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.26</version>
    </dependency>
    <!-- JavaFX 控件依赖 -->
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>17</version>
    </dependency>
</dependencies>

model/Student.java

package model;
@Data
public class Student {
    private String name;
    private int age;
}

service/StudentService.java(使用 Hutool HTTP)

package service;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import model.Student;

import java.util.Arrays;
import java.util.List;

public class StudentService {
    private final String BASE_URL = "http://localhost:8080";

    public List<Student> fetchStudents() {
        String resp = HttpRequest.get(BASE_URL + "/students").execute().body();
        return JSONUtil.toList(resp, Student.class);
    }

    public void addStudent(Student student) {
        String json = JSONUtil.toJsonStr(student);
        HttpRequest.post(BASE_URL + "/students")
                .body(json)
                .contentType("application/json")
                .execute();
    }
}

viewmodel/StudentViewModel.java

package viewmodel;

import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import model.Student;
import service.StudentService;

import java.util.List;

public class StudentViewModel {

    private final StringProperty name = new SimpleStringProperty();
    private final IntegerProperty age = new SimpleIntegerProperty();
    private final ObservableList<Student> studentList = FXCollections.observableArrayList();

    private final StudentService studentService = new StudentService();

    public void fetchStudents() {
        List<Student> students = studentService.fetchStudents();
        studentList.setAll(students);
    }

    public void saveStudent() {
        Student student = new Student(name.get(), age.get());
        studentService.addStudent(student);
        fetchStudents();
    }

    public StringProperty nameProperty() { return name; }
    public IntegerProperty ageProperty() { return age; }
    public ObservableList<Student> getStudentList() { return studentList; }
}

view/StudentView.fxml

<?xml version="1.0" encoding="UTF-8"?>
<BorderPane xmlns:fx="http://javafx.com/fxml" fx:controller="controller.StudentController">
    <center>
        <VBox spacing="10" padding="20">
            <TextField fx:id="nameField" promptText="姓名"/>
            <TextField fx:id="ageField" promptText="年龄"/>
            <HBox spacing="10">
                <Button text="保存" onAction="#onSave"/>
                <Button text="刷新" onAction="#onFetch"/>
            </HBox>
            <ListView fx:id="studentListView"/>
        </VBox>
    </center>
</BorderPane>

controller/StudentController.java(UI事件 → ViewModel)

package controller;

import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.util.converter.NumberStringConverter;
import viewmodel.StudentViewModel;

public class StudentController {

    @FXML private TextField nameField;
    @FXML private TextField ageField;
    @FXML private ListView<String> studentListView;

    private final StudentViewModel viewModel = new StudentViewModel();

    @FXML
    public void initialize() {
        nameField.textProperty().bindBidirectional(viewModel.nameProperty());
        ageField.textProperty().bindBidirectional(viewModel.ageProperty(), new NumberStringConverter());

        // 简单地绑定名称
        viewModel.getStudentList().addListener((obs, old, updated) -> {
            studentListView.getItems().clear();
            viewModel.getStudentList().forEach(s -> {
                studentListView.getItems().add(s.getName() + " (" + s.getAge() + ")");
            });
        });

        viewModel.fetchStudents();
    }

    @FXML
    public void onSave() {
        viewModel.saveStudent();
    }

    @FXML
    public void onFetch() {
        viewModel.fetchStudents();
    }
}

启动类 Main.java

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

public class Main extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        stage.setTitle("学生管理系统");
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/view/StudentView.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 400, 300);
        stage.setScene(scene);
        stage.show();
    }

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

总结

ViewModel作用

为什么要使用ViewModel,而不是controller直接调用service:
ViewModel 是“逻辑中枢”,Controller 不做业务,Controller 只做桥梁。
如果真的想在某些极简 demo 中简化,也可以跳过 ViewModel,但是只适合玩具项目。在生产系统或教学中,需要坚守 MVVM 架构。

🏆 为什么推荐 MVVM?

•	ViewModel 专注做状态和行为管理,职责单一
•	Controller 简洁清晰,没有业务逻辑
•	FXML 可绑定 ViewModel 的属性:<TextField text="${viewModel.name}" />
•	便于写单元测试,只测试 ViewModel,不依赖 UI

⚠️ 非 MVVM主要问题

•	视图字段(原本ViewModel) name/age/students 都散落在 Controller,状态不集中
•	UI 控件和数据逻辑搅在一起,难维护
•	无法绑定:TextField 不支持自动双向绑定
•	无法测试:Controller 依赖 UI 元素,无法单元测试

总结:
非 MVVM 架构一时爽,MVVM 架构保项目后期不爆炸 💥。

开发体验补充推荐

  1. Scene Builder:所见即所得
    拖拖拽拽构建 FXML,一键绑定 Controller 和 ViewModel,很适合 UI 开发人员和程序员协作。

  2. IntelliJ IDEA + MVVMFX 插件
    提升开发效率,尤其适合模块化开发与大型应用维护。

posted @ 2025-07-24 21:26  秋夜雨巷  阅读(218)  评论(0)    收藏  举报