前言
在进行了一段时间的 JavaFX 初级开发后,不可避免地会出现一些问题,例如:
- 代码量过大,阅读和维护非常的麻烦。
- 大部分的代码都是页面的样式,和具体的业务逻辑不能分开,总是挤在一块。
- 不方便和其他更高级的组件交互,能做的貌似只有页面和对静态数据或者输入数据的展示与处理。
而这些问题,在 进阶的 JavaFX 开发中都可以解决。
FXML 和 Controller
首先,要理解 进阶JavaFX 开发,就得知道 FXML 和 Controller 都是什么东西。
FXML
用来存储 JavaFX 的页面信息,文件内部采用标签的形式表示组件。而且标签中一些特殊属性可以和外界的Controller 相关联,达到页面与逻辑代码分离的效果。
Controller
关于这个,学过SSM 的应该不陌生,在 JavaFX中,它的作用就是实现 FXML 中定义组件的 相关逻辑功能,比如按钮的点击,文本框的输入数据的提取等。它是一个Java文件。
举例
对于一个简单的登录页面,如果使用初级的JavaFX 开发,在Java文件中的代码可能是这样的:
// 登录的主页面
Stage loginStage = new Stage();
CommonUtils.setStageStyle(loginStage);
// 设置与父窗口之间的关系
loginStage.initModality(Modality.APPLICATION_MODAL);
// 登录容器
VBox login = new VBox(10);
GridPane loginGrid = new GridPane();
// 用户名部分
Label usernameLabel = new Label("用户名:");
usernameLabel.setStyle("-fx-font-size: 18px;");
TextField username = new TextField();
username.setPrefSize(200,30);
username.setPromptText("请输入用户名");
username.setFocusTraversable(false);
loginGrid.add(usernameLabel,0,0);
loginGrid.add(username,1,0);
// 密码部分
Label passwordLabel = new Label("密码:");
passwordLabel.setStyle("-fx-font-size: 18px;");
PasswordField password = new PasswordField();
password.setLayoutX(passwordLabel.getWidth() + 10);
password.setPrefSize(200,30);
password.setPromptText("请输入密码");
password.setFocusTraversable(false);
loginGrid.add(passwordLabel,0,1);
loginGrid.add(password,1,1);
loginGrid.setVgap(10);
// 操作栏部分
Label loginLabel = new Label("用户登录");
loginLabel.setStyle("-fx-font-size: 25px;");
/* 不需要设置具体尺寸,放置在 BorderPane中心 会覆盖设定的尺寸
login.setPrefWidth(100);
login.setPrefHeight(100);
*/
Label registryLabel = new Label("没有账号?点击注册");
// Button registryLabel = new Button("注册");
registryLabel.setStyle("-fx-font-size: 15px;" +
"-fx-text-fill: #0000ff;");
Button loginButton = new Button("登录");
// 按钮的内边距 是文本与按钮边框的距离
// loginButton.setPadding(new Insets(10,10,10,10));
// login.setMargin(loginButton,new Insets(20,20,20,100));
loginButton.setStyle("-fx-font-size: 18px;");
Label findLabel = new Label("忘记密码?");
findLabel.setStyle("-fx-font-size: 15px;" +
"-fx-text-fill: #0000ff;");
HBox operaBox = new HBox(10,registryLabel,loginButton,findLabel);
// 设置 注册 标签 与 登录 按钮的间距
operaBox.setMargin(registryLabel,new Insets(10,10,0,10));
operaBox.setMargin(findLabel,new Insets(10,0,0,10));
operaBox.setMargin(loginButton,new Insets(10,0,0,0));
// 总体调整
login.getChildren().setAll(loginLabel,loginGrid,operaBox);
```
Scene loginScene = new Scene(loginPane,1000,545);
// 设置登录页面的各种事件
// 1. 鼠标 左击登录按钮事件
// 2. 键盘 回车键事件
// 3. 注册 被点击事件
// 4. 忘记密码 被点击事件
loginButton.setOnAction(e -> {
loginNow(loginStage,username.getText(),password.getText());
} );
// 设置 输入用户名之后 TAB 转换焦点到password
username.setOnKeyPressed(key -> {
if (key.getCode() == KeyCode.TAB)
password.requestFocus();
});
password.setOnKeyPressed(key -> {
if (key.getCode() == KeyCode.ENTER)
loginNow(loginStage,username.getText(),password.getText());
});
registryLabel.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY)
Registry.registryInit(loginStage);
});
findLabel.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY)
FindPassword.findPasswordInit(loginStage);
});
loginStage.setScene(loginScene);
loginStage.show();
但是如果是采用FXML+ Controller 的话,在Java 文件中 就可以简化成这样:
public class LoginController {
public TextField username;
public PasswordField password;
public Button loginButton;
public void userLogin(ActionEvent actionEvent) throws IOException {
String username = this.username.getText();
String userPassword = this.password.getText();
Users user = uService.login(username, userPassword);
if (user != null) {
// 登录成功逻辑
} else CommonUtils.showAlert("登录失败", "用户名或密码错误", Alert.AlertType.ERROR);
}
public void passwordEnter(KeyEvent keyEvent) throws IOException {
if (keyEvent.getCode() == KeyCode.ENTER) {
userLogin(new ActionEvent());
}
}
public void register(MouseEvent mouseEvent) throws IOException {
if (mouseEvent.isPrimaryButtonDown()){
// 注册逻辑
}
}
public void findPassword(MouseEvent mouseEvent) throws IOException {
if (mouseEvent.isPrimaryButtonDown()){
// 寻找密码逻辑
}
}
private UserService uService;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
uService = BeanUtils.getBean(UserService.class);
}
}
那这个时候就有人反驳了,博主,即使 Controller 中内容这么少,你FXML 不也有很多代码吗,而且如果是标签的话,只会比原来初级JavaFX 中的代码多吧。
是这样,但是FXML里的页面可以通过拖拉拽的形式直接生成,要做的也只是将组件和Controller绑定(通过fx:id 属性)SceneBuilder的下载与配置
SceneBuilder是一个专门服务于 JavaFX 中 FXML 文件的软件。它可以通过拖拉拽的形式快速生成页面并且同步到FXML文件中
下载地址:Scene Builder - Gluon (gluonhq.com)在IDEA中进行配置
下载并安装完成后,在IDEA中点开设置-->语言和框架--> JavaFX
将SceneBuider.exe的路径填入即可使用
先在JavaFX 项目中新建一个FXML 文件,然后配置controller,之后 右击该文件,选择 在SceneBuilder 打开,
绑定的Controller 为 fxml.login 包下的 LoginController类,后续该FXML中的组件都可以成为该Controller 中的属性值。
打开后 在 SceneBuilder 中的效果如下:
使用时将组件拖到中心预览区即可在组件结构中看到具体位置,在预览区看到具体效果。
注意事项
- SceneBuilder 中并未指出各种组件之间是否可以互相包含.
- SceneBuilder 中并未给组件的属性配备说明,一些不合理的数值也可能生效。
- SceneBuilder 中并不能直接调整对应的Controller中的内容,需要手动去生成。 使用时需要具备一定的 JavaFX 基础
Controller 配置
在FXML中配置了对应的Controller,而且也在SceneBuilder定义好了页面,应该怎样去调整Controller 的内容呢?
导入属性值
在FXML文件中将 需要导入到Controller中的组件 添加属性值 fx:id = "属性名",这个属性名将同步到 Controller 作为组件对象的变量名。
添加完成后,光标移动到fx:id上 按下 ALT+ENTER,然后回车即可同步到Controller中。导入事件
在FXML文件中将 需要导入事件的组件 添加对应事件名的属性,例 onKeyPressed="#passwordEnter",这个事件属性的值将作为Controller中的方法名。
添加完成后,光标移动到事件属性上 按下 ALT+ENTER,然后回车即可同步到Controller中。注意事项
- 即使FXML中声明了fx:id或者事件名的属性,如果不手动同步到Controller中,程序不会报错,对应的事件逻辑将不会执行。
FXML的调用方式
由于没有了代表性的 Stage ,FXML 文件页面在加载时不能通过简单的 show() 方法呈现到桌面。需要记住FXML文件的路径,使用FXML的加载器去加载,并返回一个 Pane 对象,可以参考以下代码:
AnchorPane login = new FXMLLoader(getClass().getResource("/fxml/login/login.fxml")).load();
上述代码中 AnchorPane 是 login.fxml组件结构中最外层的容器,如果这个返回值的类型不符合,程序将报错。
返回的 Pane 对象 可以封装进 Scene 进而封装进 Stage 进行展示,总结
- JavaFX的 进阶开发采用了页面与逻辑分离的思想,极大程度的提高了程序的可维护性与可阅读性。
- SceneBuilder 可以通过 组件的拖拉拽 快速生成FXML页面代码,但是不能调整对应的Controller。
- FXML 需要通过 FXMLLoader 的加载才能转换成 Java对象,进而进行相关的展示。