Files
JavaFX-Plus/README.md
2019-07-24 23:17:13 +08:00

16 KiB
Raw Blame History

JavaFX-Plus

1.1 前言

1.1.1 为什么要出这个框架

输入图片说明

记得从刚开始学习Java就开始接触JavaFX从一开始的代码编写图形到后来通过FXML编写界面一步步的学习之中逐渐领悟JavaFX的强大与灵活我对JavaFX这门生不逢时的技术有了独特的感情可以说JavaFX的强大不被许多人了解。

随着不断深入我也渐渐发现JavaFx的设计思想在很多时候是无法满足当代程序开发思想的并且一些功能并不是特别容易被使用所以特定开发了一套简化开发JavaFx开发过程的框架供大家使用希望能够简化大家的操作将精力专注于主要业务。

下面是我在开发过程中遇到的一些问题,我也针对这些问题做了简化操作。

1.1.2 FX缺点1 : 单一控制器臃肿

JavaFX中似乎都是一个Controller把所有的操作和控件囊括在里面一个Controller有几百行甚至几千行程序虽然不用考虑模块之间调用问题了但是这几千行的代码却很难被管理。

输入图片说明

图1 臃肿的controller

1.1.3 FX缺点2 : 控制类控制能力弱

JavaFX启动的Stage和Controller之间总是隔着远远的距离并且由于Controller是由JavaFX注入生成的所以很多非Controller的东西与Controller交流导致了不得不得使用静态方法或者静态成员这类小技巧来实现交流导致代码变"丑"

1.1.4 FX缺点3 : JavaBean无法使用Property

JavaFX的设计哲学是所有的JavaBean的属性都是property类型的可是很多时候我们的JavaBean都是StringInteger这类基本类型要重新修改类属性所带来的问题就足以让人让而却步了。

//普通JavaBean对象
public class Student {

    private String name;

    private int age;

    private  String gender;

    private  String code;

    
}
//简单的JavaFX bean对象
class Bill {
      // 定义一个变量存储属性
      private DoubleProperty amountDue = new SimpleDoubleProperty();

      // 定义一个getter方法获取属性值
      public final double getAmountDue(){return amountDue.get();}

      // 定义一个setter方法设置属性值
      public final void setAmountDue(double value){amountDue.set(value);}

      // 定义一个getter方法获取属性本身
      public DoubleProperty amountDueProperty() {return amountDue;}

}

1.1.5 总结

为了解决上述问题我开发了一套增强JavaFX功能的框架来起到简化JavaFX开发过程的问题。

1.2 特色一:模块化开发

1.2.1 介绍

在Java开发过程中很多界面是相似或者重复的如果能够将这些界面打包成为一个自定义控件并且通过Scenebuilder拖动就能产生一个控件那将会大大提高我们的开发效率。所以我们提出将不同区域划分为不同的子模块已达到减少耦合和加速并行开发。一般我们经常把界面分为顶部工具栏左边导航栏右侧的内容栏如果全部内容都写在一个Controller那么将会导致十分臃肿我们希望将不同的区域划分开来分而治之。

1.2.2 如何创建模块

只要新建一个类继承自FXBaseController而FXBaseController是继承于Pane这就是JavaFX-Plus的设计思想之一切皆为Pane。在类上标上FXController注解提供FXML文件的地址。如果设置为FXWindow那么将会把这个Controller以单独的Window显示这里仅仅几句代码就实现了一个简单的窗口程序。

输入图片说明

图2 Controller配置

输入图片说明

图3 显示结果

1.2.3 scenebuilder中导入刚刚生成的上面的控件

输入图片说明

图4 模块化操作

1.3 特色2 :信号机制

有两个主要标签一个是FXSender这个标签作用在方法上标记这个方法为信号发射方法。可以通过设置name修改这个信号发射方法的名称默认是函数名字。

发射信号会被订阅这个发射函数的所有FXReceiver接收并且发射函数的返回值会作为参数传进这个函数之中。而且这种发送和接受关系是全局的只要是注册了的Controller都可以进行接受不局限于同一个Controller。

我们通过一个简单的代码来理解一下。

@FXController(path = "Main.fxml")
@FXWindow(title = "demo1")
public class MainController extends FXBaseController{

    @FXML
    Button btn;

    @FXML
    Label label;
    /**
    鼠标之后,系统通过会发射信号,调用所有订阅这个发射信号函数的方法响应信号
    */
    @FXML //绑定鼠标点击事件
    @FXSender //标注为信号发射函数
    public String send(){
        System.out.println("before sending"); //输出 before sending
        return "sending msg";
    }
    /** 
        接受者必须指定要订阅的发送者类名+方法名 
        而且发送函数的返回值会注入到接受函数的参数中
   */
    @FXReceiver(name = "MainController:send")
    public void read(String msg){
        System.out.println("read " + msg); //输出 read sending msg
    }

}

输入图片说明

1.4 特色3 :JavaBean 和 JavaFxBean

一般我们写的JavaBean都是基本类型的但是JavaFXBean的设计哲学是这些属性都应该是JavaFX定义的Property类型这十分不利于我们的开发我们如何在不修改JavaBean的条件下使用到JavaFX的Property的一些优良方法呢答案是我们通过反射获得基本类型对应的Property目前仅限于booleandoubleintegerlongstringfloat等基本类型不支持List等封装对象。

输入图片说明

而本次设计的过程中希望尽量避免操作界面相关的Property等方法而是直接操作JavaBean类。例如下面代码。

@FXController(path = "Main.fxml")
@FXWindow(title = "demo1")
public class MainController extends FXBaseController{

    @FXML
    Button btn;

    @FXML
    Label label;

    Student student;

    int count = 1;

    @Override
    public void initialize() {
        student = (Student) FXEntityFactory.getInstance().createJavaBeanProxy(Student.class); //工厂产生一个学生
        student.setName("Jack"); //设置学生姓名
        FXEntityProxy fxEntityProxy = FXPlusContext.getProryByBeanObject(student); //获取学生代理
        Property nameProperty = fxEntityProxy.getPropertyByFieldName("name"); //获取Bean对应的Property
        //可以通过fxEntityProxy.getPropertyByFieldName("list"); 获得List的Property
        label.textProperty().bind(nameProperty); //属性绑定
    }

    @FXML
    @FXSender
    public String send(){
        student.setName("Jack :" + count); //操作会自动反应到界面上,无需再手动操作界面元素,专心业务部分。
        count++;
        return "sending msg";
    }

}
@FXEntity
public class Student {

    @FXField
    private String name; //标记这个类要生成property对象

    private int age;

    private  String gender;

    private  String code;

    @FXField
    private List<String> list = new ArrayList<>(); //标记这个List要生成Property对象

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public void addList(String word){
        list.add(word);
    }
    public void delList(String word){
        list.remove(word);
    }
}


实现效果是:

输入图片说明

直接操作JavaBean类就会通过动态绑定修改界面不需要讲JavaBean转换为JavaFX Bean可以减少开发中的类型转换。

2.1 如何使用这个框架

2.1.1 了解内置注解

名字 作用 参数 要求
@FXScan 扫描@FXEntity和@FXController注解标记的类 要扫描的目录 默认当前目录之下所有
@FXController 标记这个类为控件 fxml文件地址
@FXWindow 标记这个控件要以单独窗口显示 title是窗口名字也可以设置窗口长度宽度
@FXEntity 标记JavaBean系统会自动识别@FXField然后包装JavaBean为JavaFXBean 重命名
@FXField 代表这个属性要映射为Property属性
@FXSender 信号发送者 name可以重命名信号
@FXReceiver 信号接收函数 name是订阅的发射者函数名 不可空

2.1.2 两个工厂和一个context

在JavaFX-Plus中所有Controller对象和FXEnity对象都必须通过工厂创建。

student = (Student) FXEntityFactory.getInstance().createJavaBeanProxy(Student.class); //工厂产生一个学生 

通过工厂创建JavaBean在创建同时工厂会对JavaBean代理并且包装对应的Property属性。

MainController mainController = (MainController)FXFactory.getFXController(MainController.class); 

3.1 第一个Demo如何使用框架创建第一个程序

@FXScan(base = {"cn.edu.scau.biubiusuisui.example"}) //会扫描带FXController和FXEntity的类进行初始化
public class Demo extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        FXPlusApplication.start(Demo.class);  //其他配置和JavaFX相同这里要调用FXPlusAppcalition的start方法开始FX-plus加强
    }
}

接下来我们生成FXML和Controller

@FXController(path = "Main.fxml")
@FXWindow(title = "demo1")
public class MainController extends FXBaseController{

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private Button addBtn;

    @FXML
    private Button delBtn;

    @FXML
    private ListView<String> list;

    Student student;

    @FXML
    void addWord(ActionEvent event) {
        student.addList("hello" );
    }

    @FXML
    void delWord(ActionEvent event) {
        student.delList("hello");
    }

    @Override
    public void initialize() {
        student = (Student) FXEntityFactory.createJavaBeanProxy(Student.class);
        Property property = FXPlusContext.getEntityPropertyByName(student, "list");
        list.itemsProperty().bind(property);
    }
}

Studen类的定义如下


@FXEntity
public class Student {

    @FXField
    private String name;

    @FXField
    private List<String> list = new ArrayList<>();

    public void addList(String word){
        list.add(word);
    }
    public void delList(String word){
        list.remove(word);
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.Pane?>

<fx:root prefHeight="400.0" prefWidth="600.0" type="Pane" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="cn.edu.scau.biubiusuisui.example.MainController">
   <children>
      <Button fx:id="addBtn" layoutX="432.0" layoutY="83.0" mnemonicParsing="false" onAction="#addWord" text="add" />
      <Button fx:id="delBtn" layoutX="432.0" layoutY="151.0" mnemonicParsing="false" onAction="#delWord" text="del" />
      <ListView fx:id="list" layoutX="42.0" layoutY="51.0" prefHeight="275.0" prefWidth="334.0" />
   </children>
</fx:root>

从我们代码可以看出我们很少有操作界面的操作并且我们操作的对象都是基本类型的对象这样的操作十分有利于我们将普通的项目转换为JavaFX项目最终运行起来将会是这样

输入图片说明

3.2 可拖动窗口和可伸缩窗口

在Javafx中如果一个窗口隐藏了标题栏那么这个窗口也就没办法拖动和伸缩了在JavaFX-Plus中你就不需有这种烦恼只需要在@FXWindow中设置

@FXWindow(title = "demo1",dragable = true,style = StageStyle.UNDECORATED)

就可以让这个没有标题的窗口可以被拖动而且能拉伸(默认打开,可以关闭)

输入图片说明

输入图片说明

Spring支持

可以快速支持Spring和这个框架的融合只需要一行代码就可将实例的生成控制转交给容器管理。 代码如下:

@FXScan(base = {"cn.edu.scau.biubiusuisui.example.springDemo"})
public class SpringDemo extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //启动spring
        FXPlusApplication.start(SpringDemo.class, new BeanBuilder() {
            @Override
            public Object getBean(Class type) {
                return  context.getBean(type); //接管FXPlus属性的创建
            }
        });
    }
}

EL表达式绑定

在JavaFX控件的字段上面添加@FXbind可以绑定属性类似于Vue中的界面绑定但是不同的是这里的绑定可以是普通Bean和View绑定可以是View和View绑定也可以是Bean和Bean绑定不推荐。 如下面代码通过FXBind将Studen的姓名与文本框输入内容绑定学生的密码和密码框输入框内容绑定完全简化了数据传递操作代码中完全没有出现界面数据传输到控制器代码。 例子:

    @FXData
    @FXBind(
            {
                    "name=${usr.text}",
                    "password=${psw.text}"
            }
    )
    Student student = new Student();

    @FXML
    private PasswordField psw;
    @FXML
    private Label label;

    @FXML
    void login(ActionEvent event) {
        System.out.println("user:" + student.getName());
        System.out.println("psw:" + student.getPassword());
        if ("admin".equals(student.getName()) && "admin".equals(student.getPassword())) {
            System.out.println("Ok");
        } else {
            System.out.println("fail");
        }
    }
    

如图所示: 输入图片说明