前面介绍了JavaFX的常见控件用法,虽然JavaFX控件比起AWT与Swing要好用些,但是一样通过代码编写控件界面,并没有提高什么开发效率。要想浏览界面的展示效果,都必须运行测试程序才能观看,即使只是微调控件的大小,也得重新运行程序查看效果,显然既费时又费力。为此JavaFX提供了另一种给界面排版的方式,不必使用Java代码堆砌控件,而是利用FXML文件开展界面布局,同时借助于idea的预览功能,无需运行程序即可直接观察FXML的布局效果。所谓“FXML”意思是JavaFX专用的XML格式,它基于XML标准并加以扩展,每个JavaFX控件均有对应的XML标签,把这些蕴含控件的标签组装起来,便形成了一个窗口界面专属的布局文件。
举个简单的例子,现在准备画登录界面,包含用户名输入框、密码输入框,以及登录按钮,这些控件自上往下分成三行排列。该界面预期的展示效果如下图所示。

倘若完全使用代码实现以上的登录界面,无疑要反复地调整代码并多次执行程序,才能达到满意的布局效果。那么采用FXML方式的话,可以把与界面相关的控件元素剥离出来,改为在FXML文件中书写XML标签结构。比如上述登录页对应的FXML文件名叫login.fxml,其内容示例如下:

<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.PasswordField?> <FlowPane xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="5" vgap="5">
<HBox fx:id="hbUser" prefWidth="400" prefHeight="40">
<Label fx:id="labelUser" prefWidth="120" prefHeight="40" text="用户名:" />
<TextField fx:id="fieldUser" prefWidth="280" prefHeight="40" />
</HBox>
<HBox fx:id="hbPassword" prefWidth="400" prefHeight="40">
<Label fx:id="labelPassword" prefWidth="120" prefHeight="40" text="密 码:" />
<PasswordField fx:id="fieldPassword" prefWidth="280" prefHeight="40" />
</HBox>
<Button fx:id="btnLogin" prefWidth="400" prefHeight="40" text="登  录" />
</FlowPane>

在idea中打开login.fxml,注意到该文件界面的左下角有两个选项卡,一个叫“Text”,另一个叫“Scene Builder”。当前打开的login.fxml展示为文本内容,对应的正是“Text”选项,此时单击右边的“Scene Builder”选项,原先的文本内容迅速变为一组可视化页面,页面中央呈现着login.fxml的预览效果,正如下图所示。

原来fxml文件类似于html文件,尽管html文件内部充斥着各种文本标签,但使用浏览器打开html文件总能看到排版精美的网页;而idea自带的Scene Builder承担了fxml浏览器的角色,只要程序员修改了fxml文件的格式内容,切换至“Scene Builder”选项就能立刻看见修改后的界面效果,比起传统的运行程序看效果的方式,Scene Builder的渲染速度要快得多。
回头再看前述的login.fxml,它的文件内容分为两大块,前面一块形如“<?import ***?>”,其作用是导入指定包名路径的控件,与Java代码的import语句相似;后面一块包含各级控件的嵌套结构,其标签格式为“<控件名称 属性列表></控件名称>”,如果当前控件不存在下级控件,则它的标签格式可简化为“<控件名称 属性列表 />”。依据login.fxml的标签内容,可知该界面采取FlowPane流式窗格,且流式窗格拥有下列三类控件:
1、容纳用户名组件的水平箱子HBox,它的编号是hbUser。该箱子内部又有编号为labelUser的用户名标签,以及编号为fieldUser的用户名输入框;
2、容纳密码组件的水平箱子HBox,它的标识为hbPassword。该箱子内部又有编号为labelPassword的密码标签,以及编号为fieldPassword的密码输入框;
3、编号为btnLogin的登录按钮;
引入FXML布局之后,Java代码要改为从指定的fxml文件中加载界面,也就是将场景的创建过程改成如下两行代码:

		// 从FXML资源文件中加载程序的初始界面
Parent root = FXMLLoader.load(getClass().getResource("login.fxml"));
Scene scene = new Scene(root, 410, 240); // 创建一个场景

于是绘制界面的JavaFX代码缩小到了下面寥寥几行:

//登录窗口的程序入口(FXML布局控件)
public class LoginMain extends Application { @Override
public void start(Stage stage) throws Exception { // 应用程序开始运行
stage.setTitle("登录窗口"); // 设置舞台的标题
// 从FXML资源文件中加载程序的初始界面
Parent root = FXMLLoader.load(getClass().getResource("login.fxml"));
Scene scene = new Scene(root, 410, 240); // 创建一个场景
stage.setScene(scene); // 设置舞台的场景
stage.setResizable(true); // 设置舞台的尺寸是否允许变化
stage.show(); // 显示舞台
} public static void main(String[] args) {
launch(args); // 启动JavaFX应用,接下来会跳到start方法
}
}

接着运行上面的LoginMain程序,弹出的登录界面正如预期所示。

JavaFX的绝大多数静态控件,都能以单个标签的形式添加到fxml之中,除了前面例子提及的流式窗格FlowPane、水平箱子HBox、按钮Button、标签Label、文本输入框TextField、密码输入框PasswordField,还包括网格窗格GridPane、边界窗格BorderPane、垂直箱子VBox、多行输入框TextArea、复选框CheckBox、下拉框ComboBox等。然而单选按钮RadioButton的添加方式别具一格,缘由在于好几个单选按钮要构成一个按钮小组,这样才能让同组的单选按钮联动起来。因此fxml得先声明一个ToggleGroup标签,并给它分配标签编号,然后在RadioButton标签后面添加toggleGroup属性,指定加入前一步的ToggleGroup编号。操作单选按钮的具体fxml片段示例如下:

	<HBox fx:id="hbType" prefWidth="400" prefHeight="40">
<Label fx:id="labelType" prefWidth="120" prefHeight="40" text="登录类型:" />
<fx:define>
<ToggleGroup fx:id="tgType" />
</fx:define>
<RadioButton fx:id="rbPassword" prefWidth="140" prefHeight="40" toggleGroup="$tgType"
text="密码登录" selected="true" />
<RadioButton fx:id="rbVerifycode" prefWidth="140" prefHeight="40" toggleGroup="$tgType"
text="验证码登录" />
</HBox>

当然,新来的RadioButton和ToggleGroup也要在fxml头部添加以下的导入语句:

<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?>

把与单选按钮有关的xml标签补充到login.fxml,再另存为新的fxml文件名叫login_with_flow.fxml,完整的文件内容如下所示:

<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?> <FlowPane xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="5" vgap="5">
<HBox fx:id="hbType" prefWidth="400" prefHeight="40">
<Label fx:id="labelType" prefWidth="120" prefHeight="40" text="登录类型:" />
<fx:define>
<ToggleGroup fx:id="tgType" />
</fx:define>
<RadioButton fx:id="rbPassword" prefWidth="140" prefHeight="40" toggleGroup="$tgType"
text="密码登录" selected="true" />
<RadioButton fx:id="rbVerifycode" prefWidth="140" prefHeight="40" toggleGroup="$tgType"
text="验证码登录" />
</HBox>
<HBox fx:id="hbUser" prefWidth="400" prefHeight="40">
<Label fx:id="labelUser" prefWidth="120" prefHeight="40" text="用户名:" />
<TextField fx:id="fieldUser" prefWidth="280" prefHeight="40" />
</HBox>
<HBox fx:id="hbPassword" prefWidth="400" prefHeight="40">
<Label fx:id="labelPassword" prefWidth="120" prefHeight="40" text="密 码:" />
<PasswordField fx:id="fieldPassword" prefWidth="280" prefHeight="40" />
</HBox>
<Button fx:id="btnLogin" prefWidth="400" prefHeight="40" text="登  录" />
<Label fx:id="labelLoginResult" prefWidth="400" prefHeight="40" text="这里显示登录结果" />
</FlowPane>

然后利用Scene Builder观察login_with_flow.fxml的预览界面,由下图可见单选按钮组合已经添加至登录窗口的上方。

接着修改LoginMain.java,将创建场景时加载的资源文件名改为login_with_flow.fxml,再重新运行测试程序,此时弹出的登录界面如下图所示:

比较Scene Builder的预览界面,以及实际运行的窗口界面,总体而言两者大同小异,基本的布局排列是吻合的。


更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(一百四十三)FXML布局的基本格式的更多相关文章

  1. Java开发笔记(八十三)利用注解技术检查空指针

    注解属于比较高级的Java开发技术,前面介绍的内置注解专用于编译器检查代码,另外一些注解则由各大框架定义与调用,像Web开发常见的Spring框架.Mybatis框架,Android开发常见的Butt ...

  2. Java开发笔记(二十三)数组工具Arrays

    数组作为一种组合形式的数据类型,必然要求提供一些处理数组的简便办法,包括数组比较.数组复制.数组排序等等.为此Java专门设计了Arrays工具,该工具包含了几个常用方法,方便程序员对数组进行加工操作 ...

  3. Java开发笔记(三十三)字符包装类型

    正如整型int有对应的包装整型Integer那样,字符型char也有对应的包装字符型Character.初始化字符包装变量也有三种方式,分别是:直接用等号赋值.调用包装类型的valueOf方法.使用关 ...

  4. Java开发笔记(四十三)更好用的本地日期时间

    话说Java一连设计了两套时间工具,分别是日期类型Date,以及日历类型Calendar,按理说用在编码开发中绰绰有余了.然而随着Java的日益广泛使用,人们还是发现了它们的种种弊端.且不说先天不良的 ...

  5. Java开发笔记(五十三)关键字final的用法

    前面介绍了多态的相关用法,可以看到一个子类从父类继承之后,便能假借父类的名义到处晃悠.这种机制在正常情况之下没啥问题,但有时为了预防意外发生,往往只接受当事人来处理,不希望它的儿子乃至孙子来瞎掺和.可 ...

  6. Java开发笔记(六十三)双冒号标记的方法引用

    前面介绍了如何自己定义函数式接口,本文接续函数式接口的实现原理,阐述它在数组处理中的实际应用.数组工具Arrays提供了sort方法用于数组元素排序,可是并未提供更丰富的数组加工操作,比如从某个字符串 ...

  7. Java开发笔记(七十三)常见的程序异常

    一个程序开发出来之后,无论是用户还是程序员,都希望它稳定地运行,然而程序毕竟是人写的,人无完人哪能不犯点错误呢?就算事先考虑得天衣无缝,揣着一笔巨款跑去岛国买了栋抗震性能良好的海边别墅,谁料人算不如天 ...

  8. Java开发笔记(序)章节目录

    现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...

  9. Java开发笔记(一百四十五)FXML布局的伸展适配

    前面介绍了FXML的基本格式及其控制器的用法,算是打通了FXML方式的编码流程.程序界面通常保持固定尺寸,不过有时也允许用户拖曳窗口大小,不拖不打紧,一拖就可能坏事.像之前的登录窗口,没拖的时候界面如 ...

随机推荐

  1. ES6 let 和const

    一.var 的缺陷:var 只有全局作用域和函数作用域,没有块级作用域,除了在函数内部的变量默认都属于window var foo='hello'; fn=function(){ var foo='w ...

  2. Problem 2 旅行计划 (travelling .cpp)———2019.10.6

    lth tql,lzpclxf tql Orz Problem 2 旅行计划 (travelling.cpp)[题目描述]小 Z 打算趁着暑假,开启他的旅行计划.但与其他同学不同的是,小 Z 旅行时并 ...

  3. 去掉idea的mybatis烦人的xml提示

    可以在setting中自己找,也可以在顶部输入 No data sources configure  ,  SQL dialect detection  , Injected language fra ...

  4. redis(三) 集群 codis

    参考文档 http://blog.csdn.net/ztsinghua/article/details/48134377

  5. Google Kick Start 2019 C轮 第一题 Wiggle Walk 题解

    Google Kick Start 2019 C轮 第一题 Wiggle Walk 题解 题目地址:https://codingcompetitions.withgoogle.com/kickstar ...

  6. 【基础】Qt SCXML Calculator QML Example

    Qt SCXML Calculator QML Example 这个系统自带的例子原本主要是用来说明SCXML机制的,但是由于计算器的经典和简洁,我认为用来练习QML非常合适,原本的例子还有一些问题, ...

  7. tp的增删改查的结果判断?

    参考: https://blog.csdn.net/qq_27930635/article/details/78853908 总之, 要用 全等 来判断, = = = 注意, 不要再用 mysql_a ...

  8. SpringMVC 事件监听 ApplicationListener

    1. 实现 ApplicationListener<T> 接口(T为监听类型,稍后会列出具体可监听事件) 2. 将该自定义监听类,注册为Spring容器组件.(即将该类注入Spring容器 ...

  9. android分渠道打包,监测日活量统计(基于友盟SDK)

    客服说要看App日活,让加个统计功能. (我们技术部已经混到客服部都能直接提需求的地步) 首先接入友盟统计的SDK,在项目外层的build.gradle中添加依赖'https://dl.bintray ...

  10. JavaScript 反射和属性赋值!

    function Antzone(){ this.webName="蚂蚁部落"; this.age=6; } Antzone.prototype={ address:"青 ...