javafx mvvm 框架,实现视图与模型的双向绑定

kfyty-javafx

javafx mvvm 框架,实现视图和模型的双向绑定。

快速开始

引入pom(暂未发布到中央仓库,需源码安装)

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <!-- 设置父模块,集成了打包插件 -->
        <groupId>com.kfyty</groupId>
        <artifactId>kfyty-framework</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>javafx-demo</artifactId>

    <properties>
        <!-- 设置父模块后,需要设置自己的启动类,因为父模块打包后,是基于 jar index 启动的,启动速度更快 -->
        <boot-start-class>com.kfyty.javafx.demo.Main</boot-start-class>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.kfyty</groupId>
            <artifactId>kfyty-boot</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.kfyty</groupId>
            <artifactId>kfyty-javafx</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <!-- 继承 kfyty-framework 父模块后,需要添加这个插件构建 jar index 索引 -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

编写 hello-view.fxml

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.control.TextField?>
<FlowPane fx:id="root" alignment="CENTER" minWidth="200" vgap="20" prefWrapLength="150" xmlns:fx="http://javafx.com/fxml" fx:controller="com.kfyty.javafx.demo.HelloController">
    <Label text="姓名: " /> <TextField fx:id="name" />
    <Label text="部门: " /> <TextField fx:id="deptName" editable="false" onMouseClicked="#showSelectedWindow"/>

    <Button text="获取数据" onAction="#getFormData"/>
</FlowPane>

编写 HelloController

package com.kfyty.javafx.demo;

import com.kfyty.core.autoconfig.annotation.EventListener;
import com.kfyty.core.event.GenericApplicationEvent;
import com.kfyty.core.lang.Lazy;
import com.kfyty.javafx.core.AbstractController;
import com.kfyty.javafx.core.annotation.FController;
import com.kfyty.javafx.core.annotation.FView;
import com.kfyty.javafx.demo.model.User;
import jakarta.annotation.Resource;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

/**
 * {@link FController} 标识是一个 fxml 控制器,并且是一个单例控制器
 * <p>
 * 添加 {@link com.kfyty.javafx.core.annotation.FPrototypeScope} 注解可标识一个原型作用域,每次新开窗口都创建新的窗口
 */
@FController(value = "root", path = "/fxml/hello-view.fxml", title = "hello", main = true)
public class HelloController extends AbstractController {
    /**
     * 文本框的 text 绑定到 user 对象的 name 属性
     */
    @FXML
    @FView("text:user.name")
    private TextField name;

    /**
     * 文本框的 text 绑定到 user 对象的 dept 属性中的 deptName 属性
     */
    @FXML
    @FView("text:user.dept.deptName")
    private TextField deptName;

    /**
     * 由于窗口的创建必须在 {@link javafx.application.Application#launch(String...)} 之后
     * 因此要使用自动注入,需要包装为 {@link Lazy} 进行懒注入
     * <p>
     * 新开窗口时,应调用 {@link Lazy#create()} 方法,具体创建新窗口还是返回原窗口,由 ioc 容器管理
     */
    @Resource
    private Lazy<Stage> newWindow;

    /**
     * 要绑定的模型
     */
    private User user;

    /**
     * 事件监听机制
     */
    @EventListener
    public void onCloseNewWindow(GenericApplicationEvent<WindowEvent, String> event) {
        System.out.println(STR."on close new winow event: \{event.getSource()}");
    }

    /**
     * 直接从模型中获取视图数据
     */
    @FXML
    protected void getFormData() {
        NewWindowController childController = this.getChildController("newWindow", NewWindowController.class);
        System.out.println(STR."\{this.user}, child form: \{childController == null ? null : childController.getSelected().get()}");
    }

    /**
     * 打开新窗口并传值
     */
    @FXML
    protected void showSelectedWindow() {
        this.openWindow(this.newWindow.create(), "deptNames.value[0]=dept1&deptNames.value[1]=dept2");
    }

    /**
     * 模型数据绑定异常回调
     */
    @Override
    public void onModelBindCause(ObservableValue<?> target, String bindPath, Object value, Throwable throwable) {
        System.out.println(STR."\{target}:\{throwable.getMessage()}");
    }

    /**
     * 子窗口关闭回调
     */
    @Override
    public void onChildClose(String name, Node child, AbstractController controller) {
        System.out.println(STR."on child close name=\{name}, window=\{controller.getWindow()}");

        /**
         * 设置子窗口选择的值
         */
        if (controller instanceof NewWindowController newWindowController) {
            this.user.getDept().setDeptName(newWindowController.getSelected().get());
        }
    }
}

编写 new-window.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="newWindow" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="com.kfyty.javafx.demo.NewWindowController">
    <children>
        <Button layoutX="400.0" layoutY="100.0" text="添加" onAction="#addDeptName" />
        <Button layoutX="400.0" layoutY="150.0" text="删除" onAction="#delChoose" />
        <Button layoutX="400.0" layoutY="200.0" text="选择" onAction="#closeWindow" />
        <ListView fx:id="deptList" layoutX="42.0" layoutY="51.0" prefHeight="275.0" prefWidth="334.0"/>
    </children>
</AnchorPane>

编写 NewWindowController

package com.kfyty.javafx.demo;

import com.kfyty.core.event.GenericApplicationEvent;
import com.kfyty.core.lang.Value;
import com.kfyty.javafx.core.AbstractController;
import com.kfyty.javafx.core.BootstrapApplication;
import com.kfyty.javafx.core.annotation.FController;
import com.kfyty.javafx.core.annotation.FView;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.stage.WindowEvent;
import lombok.Getter;

import java.util.ArrayList;
import java.util.List;

/**
 * 示例添加了 icon,可自动添加图片,或删除该属性
 */
@FController(value = "newWindow", path = "/fxml/new-window.fxml", show = true, icon = "/icon/icon.ico")
public class NewWindowController extends AbstractController {
    /**
     * 列表数据绑定到 deptNames 属性
     * 列表选择的数据绑定到 selected 属性
     */
    @FXML
    @FView("items:deptNames.value")
    @FView(value = "getSelectionModel.selectedItemProperty:selected.value", method = true)
    private ListView<String> deptList;

    /**
     * 绑定模型
     * 基本数据类型需要使用 {@link Value} 包装
     */
    @Getter
    private final Value<List<String>> deptNames = new Value<>(new ArrayList<>());

    /**
     * 绑定模型
     * 基本数据类型需要使用 {@link Value} 包装
     */
    @Getter
    private Value<String> selected;

    private int cnt = 0;

    @FXML
    public void addDeptName() {
        this.deptNames.get().add(STR."hello\{++cnt}");
    }

    @FXML
    protected void delChoose() {
        this.deptNames.get().remove(this.selected.get());
    }

    @FXML
    protected void closeWindow() {
        this.close();
    }

    /**
     * 窗口已显示回调
     */
    @Override
    public void onShown(WindowEvent event) {
        System.out.println("show new window");
    }

    /**
     * 本窗口关闭回调
     */
    @Override
    public void onClose(WindowEvent event) {
        System.out.println(STR."publish close new window event: \{this.window}");
        BootstrapApplication.publishEvent(new GenericApplicationEvent<>(event, STR."close window: \{this.window}"));
    }
}

编写数据模型

package com.kfyty.javafx.demo.model;

import com.kfyty.core.autoconfig.annotation.NestedConfigurationProperty;
import lombok.Data;

@Data
@NestedConfigurationProperty
public class User {
    private String name;

    @NestedConfigurationProperty
    private Dept dept = new Dept();

    @Data
    public static class Dept {
        private String deptName;
    }
}

编写一个启动类

package com.kfyty.javafx.demo;

import com.kfyty.boot.K;
import com.kfyty.core.autoconfig.annotation.BootApplication;

@BootApplication
public class Main {

    public static void main(String[] args) {
        K.run(Main.class, args);
    }
}

运行 Main.main 方法即可.

仓库地址 https://github.com/kfyty/kfyty-utils/tree/master/kfyty-javafx

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值