1. 新增了多窗口切换功能

2. 新增多个example示例可供使用参考
3. 修正信号收发机制的bug
4. 规范化部分代码,并加以部分注释
5. 修改README
This commit is contained in:
suisui
2019-12-10 23:04:44 +08:00
parent 5cc7e57a88
commit c0684c7501
44 changed files with 1438 additions and 1528 deletions

514
README.md
View File

@@ -1,44 +1,54 @@
# JavaFX-Plus
- [JavaFX-Plus](#javafx-plus)
* [前言](#--)
+ [开发进程](#----)
* [特色](#--)
+ [信号机制](#----)
+ [JavaBean 和 JavaFxBean](#javabean---javafxbean)
+ [可拖动窗口和可伸缩窗口](#-----------)
+ [Spring支持](#spring--)
+ [数据表达式绑定](#-------)
- [Bean和View绑定](#bean-view--)
- [View和View绑定](#view-view--)
+ [模块化开发](#-----)
- [介绍](#--)
- [如何创建模块](#------)
- [scenebuilder中导入刚刚生成的上面的控件](#scenebuilder-------------)
* [如何使用这个框架](#--------)
+ [内置注解](#----)
+ [两个工厂和一个context](#-------context)
* [创建第一个程序](#-------)
* [前言](#前言)
+ [开发进程](#开发进程)
* [Maven仓库地址](#Maven仓库地址)
* [框架功能描述](#框架功能描述)
+ [模块化开发](#模块化开发)
- [介绍](#介绍)
- [如何创建模块](#如何创建模块)
- [scenebuilder中导入刚刚生成的上面的控件](#scenebuilder中导入刚刚生成的上面的控件)
+ [与Spring的融合](#与Spring的融合)
+ [信号机制](#信号机制)
+ [JavaBean与JavaFXBean的转换](#JavaBean与JavaFXBean的转换)
+ [可拔插功能](#可拔插功能)
+ [数据绑定](#数据绑定)
- [Bean和View绑定](#Bean和View绑定)
- [View和View绑定](#View和View绑定)
- [函数表达式绑定](#函数表达式绑定)
+ [多窗口切换功能](#多窗口切换功能)
* [框架的使用](#框架的使用)
+ [内置注解](#内置注解)
+ [两个工厂和一个context](#两个工厂和一个context)
* [创建第一个程序](#创建第一个程序)
## 前言
这个框架不是UI美化框架为了简化javaFX项目开发、为了减少项目之间组件耦合而打造的框架。目前框架主要功能如下图所示
这个框架不是UI美化框架为了简化javaFX项目开发、为了减少项目之间组件耦合而打造的框架。目前框架主要功能如下图所示
![输入图片说明](https://images.gitee.com/uploads/images/2019/0629/155142_1235eb9c_2067650.png "JavaFX-Plus.png")
![输入图片说明](doc/JavaFx-Plus.png)
### 开发进程
2019年11月25日起项目暂停更新将会下次发布将会升级为2.0版本,到时候将会提供更多数据绑定操作,以及优化性能。
- [x] 模块化
- [x] 与Spring的融合
- [x] 信号机制
- [x] 数据绑定
- [x] spring结合
- [x] JavaBean和JavaFXBean的转换
- [x] 可拔插功能(窗口拖动等功能)
- [x] 数据绑定
- [x] Bean和View的绑定
- [x] View和View的绑定
- [x] 函数表达式绑定
- [x] 多窗口切换功能
- [ ] 事件注解绑定
- [ ] 函数表达式绑定
- [ ] 数据校验
- [ ] 键盘事件绑定
- [ ] 优化性能
## 仓库地址
## Maven仓库地址
```xml
<dependency>
@@ -49,51 +59,130 @@
```
## 特色
## 框架功能描述
### 模块化开发
#### 介绍
在Java开发过程中很多界面是相似或者重复的如果能够将这些界面打包成为一个自定义控件并且通过Scenebuilder拖动就能产生一个控件那将会大大提高我们的开发效率。所以我们提出将不同区域划分为不同的子模块已达到减少耦合和加速并行开发。一般我们经常把界面分为顶部工具栏左边导航栏右侧的内容栏如果全部内容都写在一个Controller那么将会导致十分臃肿我们希望将不同的区域划分开来分而治之。
#### 如何创建模块
只要新建一个类继承自FXBaseController而FXBaseController是继承于Pane这就是JavaFX-Plus的设计思想之一切皆为Pane。在类上标上FXController注解提供FXML文件的地址。如果设置为FXWindow那么将会把这个Controller以单独的Window显示这里仅仅几句代码就实现了一个简单的窗口程序。
![输入图片说明](https://images.gitee.com/uploads/images/2019/0629/022014_83ecdbde_2067650.png "controllerConfig.png")
图2 Controller配置
![输入图片说明](https://images.gitee.com/uploads/images/2019/0629/022024_71892db3_2067650.png "demo1.png")
图3 显示结果
#### scenebuilder中导入刚刚生成的上面的控件
![输入图片说明](https://images.gitee.com/uploads/images/2019/0629/022036_e128f313_2067650.gif "modulesAction.gif")
图4 模块化操作
### 与Spring的融合
可以快速支持Spring和这个框架的融合只需要一行代码就可将实例的生成控制转交给容器管理。
代码如下:
```java
@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属性的创建
}
});
}
}
```
### 信号机制
有两个主要标签一个是FXSender这个标签作用在方法上,标记这个方法为信号发射方法。可以通过设置name修改这个信号发射方法的名称,默认是函数名字。
有两个主要标签一个是FXSender这个标签作用在函数上,标记这个方法为信号发射函数。可以通过设置name修改这个信号发射函数的名称,默认是函数名字。
发射信号会被订阅这个发射函数的所有FXReceiver接收并且发射函数的返回值会作为参数传进这个函数之中。而且这种发送和接关系是全局的只要是注册了的Controller都可以进行接不局限于同一个Controller。
发射信号会被订阅这个发射函数的所有FXReceiver接收并且发射函数的返回值会作为参数传进这个函数之中。而且这种发送和接关系是全局的只要是注册了的Controller都可以进行接不局限于同一个Controller。
我们通过一个简单的代码来理解一下。
我们通过一个简单的代码来理解一下主要实现自定义组件导航栏TopBar主界面中包含该组件当用户点击导航栏某些按钮时能返回主界面相关信息。fxml文件详见resources下的mqDemo文件夹
1. 利用JavaFX的模块化我们设计一个简单的导航栏
```java
@FXController(path = "Main.fxml")
@FXWindow(title = "demo1")
public class MainController extends FXBaseController{
@FXController(path = "mqDemo/topBar.fxml")
public class TopBarController extends FXBaseController {
@FXML
Button btn;
public void indexClick() {
sendToMain("点击[首页]");
}
@FXML
Label label;
public void scoreClick() {
sendToMain("点击[积分中心]");
}
@FXML
public void questionClick() {
sendToMain("点击[问答中心]");
}
@FXML
public void selfClick() {
sendToMain("点击[个人中心]");
}
/**
鼠标之后,系统通过发射信号,调用所有订阅这个发射信号函数的方法响应信号
*/
@FXML //绑定鼠标点击事件
@FXSender //标注为信号发射函数
public String send(){
System.out.println("before sending"); //输出 before sending
return "sending msg";
* 系统通过发射信号,调用所有订阅这个发射信号函数的方法,从而响应信号
* @param msg
* @return
*/
@FXSender //标注为信号发射函数
public String sendToMain(String msg) {
return msg;
}
/**
接受者必须指定要订阅的发送者类名+方法名
而且发送函数的返回值会注入到接受函数的参数中
*/
@FXReceiver(name = "MainController:send")
public void read(String msg){
System.out.println("read " + msg); //输出 read sending msg
}
}
```
![输入图片说明](https://images.gitee.com/uploads/images/2019/0629/022051_db8dbc7a_2067650.gif "signalshow.gif")
### JavaBean 和 JavaFxBean
2. 再设计一个主界面,里面包含导航栏
```java
@FXController(path = "mqDemo/main.fxml")
@FXWindow(mainStage = true, title = "MQDemo")
public class MainController extends FXBaseController {
@FXML
private TextArea outTA;
/**
* 接收者必须指定要订阅的[发送者类名:方法名]
* 发送函数的返回值会注入到接收函数的参数中
*
* @param msg
*/
@FXReceiver(name = "TopBarController:sendToMain")
public void handleTopBar(String msg) {
// TODO: 2019/12/8
// 处理导航栏的点击事件
outTA.appendText(msg + "\n");
}
}
```
![输入图片说明](doc/mqDemo/20191208-194336-signalshow.gif)
### JavaBean与JavaFXBean的转换
一般我们写的JavaBean都是基本类型的但是JavaFXBean的设计哲学是这些属性都应该是JavaFX定义的Property类型这十分不利于我们的开发我们如何在不修改JavaBean的条件下使用到JavaFX的Property的一些优良方法呢答案是我们通过反射获得基本类型对应的Property目前仅限于booleandoubleintegerlongstringfloatList等基本类型不支持封装对象。
@@ -203,8 +292,10 @@ public class Student {
直接操作JavaBean类就会通过动态绑定修改界面不需要讲JavaBean转换为JavaFX Bean可以减少开发中的类型转换。
### 可拖动窗口和可伸缩窗口
在Javafx中如果一个窗口隐藏了标题栏那么这个窗口也就没办法拖动和伸缩了在JavaFX-Plus中你就不需有这种烦恼只需要在@FXWindow中设置
### 可拔插功能
在本框架中实现了窗口可拖动和窗口可伸缩在Javafx中如果一个窗口隐藏了标题栏那么这个窗口也就没办法拖动和伸缩了在JavaFX-Plus中你就不需有这种烦恼只需要在@FXWindow中设置
```java
@FXWindow(title = "demo1",dragable = true,style = StageStyle.UNDECORATED)
```
@@ -215,30 +306,12 @@ public class Student {
![输入图片说明](https://images.gitee.com/uploads/images/2019/0630/135637_cb0e0a89_2067650.gif "resizeAble.gif")
### Spring支持
可以快速支持Spring和这个框架的融合只需要一行代码就可将实例的生成控制转交给容器管理。
代码如下:
```java
@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属性的创建
}
});
}
}
### 数据绑定
```
与之相关的注解有`@FXBind`注解在JavaFX控件的字段上面标明该变量的绑定方式和绑定属性类似于Vue中的界面绑定。目前已实现Bean和View的绑定、View和View的绑定、函数表达式的绑定
### 数据表达式绑定
#### Bean和View绑定
在JavaFX控件的字段上面添加@FXbind可以绑定属性类似于Vue中的界面绑定但是不同的是这里的绑定可以是普通Bean和View绑定可以是View和View绑定也可以是Bean和Bean绑定不推荐
如下面代码通过FXBind将Studen的姓名与文本框输入内容绑定学生的密码和密码框输入框内容绑定完全简化了数据传递操作代码中完全没有出现界面数据传输到控制器代码。
如下面代码,通过FXBind将Student的姓名与文本框输入内容绑定学生的密码和密码框输入框内容绑定完全简化了数据传递操作代码中完全没有出现界面数据传输到控制器代码
例子:
```java
@@ -253,8 +326,9 @@ public class SpringDemo extends Application {
@FXML
private PasswordField psw;
@FXML
private Label label;
private Label pswMsg;
@FXML
void login(ActionEvent event) {
@@ -272,7 +346,10 @@ public class SpringDemo extends Application {
如图所示:
![输入图片说明](https://images.gitee.com/uploads/images/2019/0724/231705_976181ba_2067650.gif "expression.gif")
#### View和View绑定
```java
@FXBind("text=${psw.text}")
@FXML
@@ -281,62 +358,273 @@ private Label pswMsg;//任何psw中的内容都会同步到pswMsg中
如图所示
![输入图片说明](https://images.gitee.com/uploads/images/2019/0724/232237_7e243f15_2067650.gif "expressionV2V.gif")
### 模块化开发
#### 函数表达式绑定
示例代码可见`cn.edu.scau.biubiusuisui.example.listDemo``cn.edu.scau.biubiusuisui.actionDemo`以下举actionDemo为例。
1. 使用方法
使用在界面Controller类中的JavaFX控件上与以上两种绑定类似`${}`为外部标识,当该绑定属于函数表达式绑定时,需要在函数名前加`@`
```java
@FXBind("text=${@toUs(time.text)}") // 将Label中的text和toUs()函数的返回值绑定
private Label us;
```
2. 示例代码
如以下代码,实现简单的汇率转换器。
```java
@FXController(path = "actionDemo/actionDemo.fxml")
@FXWindow(title = "actionDemo", mainStage = true)
public class MainController extends FXBaseController implements Initializable {
@FXML
@FXBind("text=${@toUs(time.text)}") // 将Label中的text和toUs()函数的返回值绑定
private Label us;
@FXML
@FXBind("text=${@toJp(time.text)}")
private Label jp;
@FXML
@FXBind("text=${@toUk(time.text)}")
private Label uk;
@FXML
private TextField time;
public String toUs(String value) {
double money = Double.valueOf(value);
double percent = 0.1454;
return String.valueOf(money * percent);
}
public String toJp(String value) {
double money = Double.valueOf(value);
double percent = 15.797;
return String.valueOf(money * percent);
}
public String toUk(String value) {
double money = Double.valueOf(value);
double percent = 0.1174;
return String.valueOf(money * percent);
}
}
```
3. 动态演示
如图所示:
![20191210-175409-actionDemo](doc/actionDemo/20191210-175409-actionDemo.gif)
### 多窗口切换功能
#### 介绍
在Java开发过程中很多界面是相似或者重复的如果能够将这些界面打包成为一个自定义控件并且通过Scenebuilder拖动就能产生一个控件那将会大大提高我们的开发效率。所以我们提出将不同区域划分为不同的子模块已达到减少耦合和加速并行开发。一般我们经常把界面分为顶部工具栏左边导航栏右侧的内容栏如果全部内容都写在一个Controller那么将会导致十分臃肿我们希望将不同的区域划分开来分而治之
在JavaFX中常常需要多个窗口之间进行切换比如登录窗口点击登录后跳转至登录成功/失败窗口网上部分有关多窗口切换的JavaFX教程实现过程为在Controller中是实现FXML绑定并通过外部调用某个show方法来切换窗口。由于本框架已经将FXML绑定这一功能封装故通过在Controller内部直接初始化Stage并设置参数这一方法并不现实。因此在本框架中编写StageController类对Controller进行管理
#### 如何创建模块
#### 涉及到的注解
只要新建一个类继承自FXBaseController而FXBaseController是继承于Pane这就是JavaFX-Plus的设计思想之一切皆为Pane。在类上标上FXController注解提供FXML文件的地址。如果设置为FXWindow那么将会把这个Controller以单独的Window显示这里仅仅几句代码就实现了一个简单的窗口程序
`@FXController`标记于类上用于绑定FXML文件需要注意的是标记了`FXController`只能标记这是一个控件
![输入图片说明](https://images.gitee.com/uploads/images/2019/0629/022014_83ecdbde_2067650.png "controllerConfig.png")
`@FXWindow`标记于类上标记这个Controller需要以窗口显示只有需要以单独窗口显示时才会被重定向。
图2 Controller配置
`@FXRedirect`:标记于函数上,标记该函数用于处理重定向。
![输入图片说明](https://images.gitee.com/uploads/images/2019/0629/022024_71892db3_2067650.png "demo1.png")
#### 规定
图3 显示结果
本框架规定,当需要使用`@FXRedirect`标记函数处理重定向时函数必须是返回String类型的函数且返回已注册的Controller名如需要重定向至登录成功界面控制器为`SuccessController`,则需要写上`return "SuccessController"。
#### scenebuilder中导入刚刚生成的上面的控件
#### 使用方法
![输入图片说明](https://images.gitee.com/uploads/images/2019/0629/022036_e128f313_2067650.gif "modulesAction.gif")
1. `FXRedirect`注解的使用如下:
图4 模块化操作
```java
@FXRedirect
public String redirectToRegister() {
return "RegisterController";
}
@FXML
@FXRedirect(close = false) //测试弹窗
public String redirectToDialog() {
return "DialogController";
}
```
## 如何使用这个框架
close是标明是否需要关闭当前的窗口默认为true即默认当跳转另一个窗口时关闭当前窗口。
2. 创建程序初始界面Controller此处举例为登录界面
```java
@FXController(path = "redirectDemo/login.fxml")
@FXWindow(title = "redirectDemo", mainStage = true)
public class LoginController extends FXBaseController {
@FXML
private TextField usernameTF;
@FXML
private PasswordField passwordPF;
@FXML
public void registerClick() {
System.out.println("点击注册.....");
redirectToRegister();
}
@FXRedirect
public String redirectToRegister() {
return "RegisterController";
}
@FXML
@FXRedirect(close = false) //测试弹窗
public String redirectToDialog() {
return "DialogController";
}
}
```
3. 编写需要跳转的界面Controller比如登录时尚无账号跳转至注册界面和测试弹窗的Controller
```java
@FXController(path = "redirectDemo/register.fxml")
@FXWindow(title = "register")
public class RegisterController extends FXBaseController {
@FXML
private TextField usernameTF;
@FXML
private TextField emailTF;
@FXML
private PasswordField passwordPF;
@FXML
private PasswordField confirmPasswordPF;
@FXML
public void registerClick() {
}
@FXML
public void loginClick() {
redirectToLogin();
}
@FXRedirect
public String redirectToLogin() {
return "LoginController";
}
}
```
```java
@FXController(path = "redirectDemo/dialog.fxml")
@FXWindow(title = "弹窗")
public class DialogController extends FXBaseController {
}
```
#### 示例演示
本次示例源码已存于`cn.edu.scau.biubiusuisui.example.redirectDemo`fxml文件于resources下的redirectDemo文件夹中。
1. 跳转至另一个窗口(关闭原窗口)
![](doc/redirectDemo/20191208-125739-close.gif)
2. 弹窗形式弹出窗口(不关闭原窗口)
![](doc/redirectDemo/20191208-125511-not close.gif)
#### 不足之处
暂未实现携带数据的窗口跳转目前只实现纯粹跳转到另一个Controller。
## 框架的使用
### 内置注解
| 名字 | 作用 | 参数 | 要求 |
| ------------- | ------------------------------------------------------------ | --------------------------------------- | -------------------- |
| @FXData | 表面这个普通bean要装配成javafxBean | fx_id | 重新命名 |
| @FXScan | 扫描@FXEntity和@FXController注解标记的类 | 要扫描的目录 | 默认当前目录之下所有 |
| @FXController | 标记这个类为控件 | fxml文件地址 | 无 |
| @FXWindow | 标记这个控件要以单独窗口显示 | title是窗口名字也可以设置窗口长度宽度 | 无 |
| @FXEntity | 标记JavaBean系统会自动识别@FXField然后包装JavaBean为JavaFXBean | 重命名 | |
| @FXField | 代表这个属性要映射为Property属性 | | |
| @FXSender | 信号发送者 | name可以重命名信号 | |
| @FXReceiver | 信号接收函数 | name订阅的发射者函数名 | 不可空 |
| 名字 | 作用 | 参数 | 要求 |
| ------------- | ------------------------------------------------------------ | --------------------------------------- | ---------------------------------------- |
| @FXData | 标明这个普通bean要装配成javafxBean | fx_id | 重新命名 |
| @FXScan | 扫描@FXEntity和@FXController注解标记的类 | 要扫描的目录 | 默认当前目录之下所有 |
| @FXController | 标记这个类为控件 | pathfxml文件地址 | 无 |
| @FXWindow | 标记这个控件要以单独窗口显示 | title是窗口名字也可以设置窗口长度宽度 | 无 |
| @FXEntity | 标记JavaBean系统会自动识别@FXField然后包装JavaBean为JavaFXBean | 重命名 | |
| @FXField | 代表这个属性要映射为Property属性 | | |
| @FXSender | 信号发送者 | name重命名信号 | |
| @FXReceiver | 信号接收函数 | name订阅的发射者函数名 | 不可空 |
| @FXRedirect | 标记函数为重定向函数 | close是否关闭当前窗口 | 返回值为某个使用了FXView注解的Controller |
### 两个工厂和一个context
1. 两个工厂
在JavaFX-Plus中所有Controller对象和FXEnity对象都必须通过工厂创建。
```
```java
student = (Student) FXEntityFactory.getInstance().createJavaBeanProxy(Student.class); //工厂产生一个学生
```
通过工厂创建JavaBean在创建同时工厂会对JavaBean代理并且包装对应的Property属性。
通过工厂创建JavaBean在创建同时工厂会对JavaBean代理并且包装对应的Property属性。
```
```java
MainController mainController = (MainController)FXFactory.getFXController(MainController.class);
```
2. 一个context
存储所有用@FXController注解后的Controller和FXEntity的代理类。
```java
public class FXPlusContext {
private FXPlusContext(){}
private static Map<String, List<FXBaseController>> controllerContext = new ConcurrentHashMap<>(); //FXController控制器注册表
private static Map<Object, FXEntityProxy> beanMap = new ConcurrentHashMap<>(); // Object注册为FXEntityObject
public static void addController(FXBaseController fxBaseController){
List<FXBaseController> controllers = controllerContext.get(fxBaseController.getName());
if(controllers == null){
controllers = new LinkedList<>();
}
controllers.add(fxBaseController);
}
public static List<FXBaseController> getControllers(String key){
return controllerContext.get(key);
}
public static FXEntityProxy getProxyByBeanObject(Object object){
return beanMap.get(object);
}
public static void setProxyByBeanObject(Object object,FXEntityProxy fxEntityProxy){
beanMap.put(object,fxEntityProxy);
}
}
```
## 创建第一个程序
1. 主程序类。
```java
@FXScan(base = {"cn.edu.scau.biubiusuisui.example"}) //会扫描带FXController和FXEntity的类进行初始化
public class Demo extends Application {
@@ -347,24 +635,20 @@ public class Demo extends Application {
}
```
接下来我们生成FXML和Controller
2. 接下来我们生成FXML和Controller
```java
@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;
@@ -382,9 +666,9 @@ public class MainController extends FXBaseController{
@Override
public void initialize() {
student = (Student) FXEntityFactory.createJavaBeanProxy(Student.class);
Property property = FXPlusContext.getEntityPropertyByName(student, "list");
list.itemsProperty().bind(property);
student = (Student) FXEntityFactory.createJavaBeanProxy(Student.class); // 从工厂中拿到将JavaBean转换得到的JavaFXBean
Property listProperty = FXPlusContext.getEntityPropertyByName(student, "list");
list.itemsProperty().bind(listProperty);
}
}
@@ -396,7 +680,7 @@ Studen类的定义如下
@FXEntity
public class Student {
@FXField
@FXField //标记该属性是否被生成对应的Property
private String name;
@FXField
@@ -411,6 +695,8 @@ public class Student {
}
```
请注意FXML文件的根标签必须为`<fx:root>`。
```xml
<?xml version="1.0" encoding="UTF-8"?>