使用 JavaFX 2.0 FXML 呈现企业应用程序的 UI — 第 2 部分

作者:James L. Weaver

使用 FX 标记语言的强大功能定义企业级应用程序的 UI。

2012 年 9 月发布

下载:

下载Java FX

下载NetBeans IDE

下载示例项目(Zip 格式)

JavaFX 2.0 是用于创建富互联网应用程序 (RIA) 的 API 和运行时。JavaFX 于 2007 年推出,2011 年 10 月发布了 2.0 版本。该版本的一个优点是可以在成熟、熟悉的工具中通过 Java 语言编写代码。

本文是该系列文章(共两部分)的第 2 部分,重点介绍了如何利用 FX 标记语言 (FXML,JavaFX 2.0 附带的一种工具)的功能快速定义企业级应用程序的用户界面。

FXML 支持使用 XML 呈现 UI。包含 FXML 功能的类位于 javafx.fxml 包中,其中包括 FXMLLoaderJavaFXBuilderFactory 以及名为 Initializable 的接口。本文在该系列第 1 部分的基础上对 SearchDemoFXML 示例进行了延伸,在其中包含了更多使用 FXML 创建企业应用程序的概念和技术。

SearchDemoFXML 应用程序概述

在本系列的第 1 部分中,使用了 SearchDemoFXML 应用程序示例来帮助您了解如何在 JavaFX 2.0 应用程序中利用 FXML。在本文中,我们将为该应用程序提供另一种 UI,并在不修改 SearchDemoController.java 控制器类的情况下用一个新的 FXML 文件来描述它。

如图 1 和图 2 所示,该示例包含一些与原示例相同的 UI 组件:

  • 一个 TextField 和一个 Button,用于搜索 iTunes 中的媒体
  • 一个 TableView ,用于显示搜索结果
  • 一个 ImageView,用于查看所选专辑的封面
  • 一个 Button,可以打开浏览器选项卡,预览所选标题的音频/视频剪辑

此外,还增加了其他组件,并修改了布局和导航。而且,该 FXML 示例中还实施了级联样式表 (CSS) 和本地化。您将在下一节中下载 SearchDemoFXML 项目,其中包含该示例的代码,本文将着重探讨其中的部分代码。

图 1:SearchDemoFXML 应用程序的屏幕截图

当应用程序启动时,如果用户机器的区域设置与 SearchDemoFXML 应用程序所支持的区域设置相匹配,则 UI 控件中的文本将本地化。图 1 中的屏幕截图显示了当应用程序运行在区域设置为 en_US 的机器上时所显示的外观。当您在文本字段中输入标题或艺术家并单击按钮后,会弹出一个表,其中包含查询 iTunes 服务器所返回的一组歌曲,如图 1 所示。

从表中选择一首歌曲,然后选择 Detail 选项卡,此时会显示所选歌曲的专辑封面,如图 2 所示。单击 Preview 按钮在默认浏览器中打开新的选项卡,预览音频或视频剪辑:

图 2:SearchDemoFXML 在搜索过程中的屏幕截图

获取并运行 SearchDemoFXML 项目

  • 下载 NetBeans 项目文件,该文件中包含 SearchDemoFXML 项目。
  • 将该项目解压缩到您选择的目录。
  • 启动 NetBeans,选择 File -> Open Project
  • 在 Open Project 对话框中,找到所选目录并打开 SearchDemoFXML 项目,如图 3 所示。如果收到一个消息对话框,告诉您无法找到 jfxrt.jar 文件,则单击 Resolve 按钮并转到 JavaFX 2.0 SDK 安装目录下面的 rt/lib 文件夹。

:可以从 NetBeans 网站获取 NetBeans IDE。

图 3:在 NetBeans 中打开 SearchDemoFXML 项目

  • 要运行该应用程序,请单击工具栏上的 Run Project 图标或按下 F6 键。Run Project 图标的外观类似于媒体(如 DVD)播放器上的 Play 按钮,如图 4 所示。

图 4:在 NetBeans 中运行 SearchDemoFXML 应用程序

SearchDemoFXML 应用程序会显示在一个窗口中,如前面图 1 中所示。继续使用该应用程序搜索歌曲、专辑和艺术家。当您准备好时,我们将继续介绍此程序版本中增加的 FXML 和相关特性。

分析 SearchDemoFXML 应用程序

在深入研究代码之前,我们先来分析图 5 所示的各个片段,这些片段共同组成了 SearchDemoFXML 应用程序。

图 5:SearchDemoFXML 应用程序示意图

从图 5 左上角开始,SearchDemo.java 是此 JavaFX 应用程序的主要模块,扩展了 Application 并包含 main()start() 方法。它在 SearchDemoFXML 应用程序中的主要作用是创建一个场景并用场景图来填充它,而场景图需通过 FXMLLoader.load() 方法从 search_demo_w_tabs.fxml 文件或 search_demo.fxml 文件获得。

为了实现这一点,应用程序会在启动时检查名为 tabs命名参数 。如果 tabs 命名参数的值为 true,则加载 search_demo_w_tabs.fxml 文件,否则加载 search_demo.fxml 文件。这就生成了图 5 左下角显示的用户界面之一,具体取决于 tabs 命名参数

图 5 顶部中间的 search_demo_w_tabs.fxmlsearch_demo.fxml 文件包含场景图的两种完全不同的 XML 表示。这两个文件还将 SearchDemoController.java 文件指定为控制器 类,它将 UI 组件发生的事件委托给控制器 中的处理器 方法。

图 5 底部中间位置的 SearchDemoController 控制器类保存 search_demo_w_tabs.fxmlsearch_demo.fxml 文件中对 UI 组件的引用,并处理这些组件发生的事件。

在图 5 右侧可以看到,SearchDemoController 调用 RestFX 类的方法来查询 iTuens 服务的 REST 接口。RestFX 是 JavaFX 2.0 外部的一个库,这里使用它来与 iTunes REST 端点通信,并分析端点的 JSON 响应。本文末尾的“另请参见”一节提供了 REST/FX 项目的链接。

在启动时加载所需的 FXML 文档

如上一节所述,SearchDemo.java 是该 JavaFX 应用程序的主要模块。如清单 1 所示:

package demos.search;

import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.SceneBuilder;
import javafx.stage.Stage;

public class SearchDemo extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
System.out.println("In SearchDemo#start()");
Object tabsParamObj;
Object languageParamObj;
Object countryParamObj;
String fxmlFile = "search_demo.fxml";
Locale locale = Locale.getDefault();
Map<String, String> namedParams = getParameters().getNamed();
if (namedParams != null) {
tabsParamObj = namedParams.get("tabs");
if (tabsParamObj != null) {
System.out.println("param tabs:"+ tabsParamObj);
if (((String)tabsParamObj).equalsIgnoreCase("true")) {
fxmlFile = "search_demo_w_tabs.fxml";
            }
          }
else {
System.out.println("param tabs not found");
          }
          
languageParamObj = namedParams.get("language");
if (languageParamObj != null) {
System.out.println("param language:"+ languageParamObj);
          }
else {
System.out.println("param language not found");
          }

countryParamObj = namedParams.get("country");
if (countryParamObj != null) {
System.out.println("param country:"+ countryParamObj);
          }
else {
System.out.println("param country not found");
          }
        
if ((languageParamObj != null) &&

((String)languageParamObj).trim().length() > 0) {
if ((countryParamObj != null) && 
((String)countryParamObj).trim().length() > 0) {
locale = new Locale(((String)languageParamObj).trim(),
((String)countryParamObj).trim());
            }
else {
locale = new Locale(((String)languageParamObj).trim());
            }
          }
        }
else {
System.out.println("No params found for SearchDemo");
        }
        
Parent root = FXMLLoader.load(getClass().getResource(fxmlFile),
ResourceBundle.getBundle("demos/search/search_demo", locale));
System.out.println("Locale set to:"+ locale);
        
primaryStage.setTitle("Search Demo");
primaryStage.setWidth(650);
primaryStage.setHeight(500);

primaryStage.setScene(
SceneBuilder.create()
.root(root)
.stylesheets("demos/search/myStyles.css")
.build()
        );
primaryStage.show();
    }

public static void main(String[] args) {
launch(args);
    }
}

清单 1:SearchDemo.java

如清单 1 所示,已获取三个应用程序特定命名参数的值:tabslanguagecountry

  • tabs 指示 FXMLLoader.load() 方法是加载 search_demo_w_tabs.fxml 文档还是 search_demo.fxml 文档。
  • language 指示使用哪种语言来执行应用程序本地化。如果未提供 language 参数,则采用默认区域设置所指定的语言。
  • country 指示使用哪种语言来执行应用程序本地化。如果未提供 country 参数,则采用默认区域设置所指定的国家/地区。

如清单 1 所示,此时会调用 FXMLLoader.load() 方法来加载所需的 FXML 文档和ResourceBundle

要在 SearchDemoFXML 项目中尝试这些参数,您可以在 NetBeans 中将参数输入到 Project Properties 对话框的 Run 部分,如图 6 所示。

图 6:NetBeans 中 Project Properties 对话框中的 Run 部分

正如本系列第 1 部分所述,加载 FXML 文档之后,场景图将被实例化并分配给与阶段相关的场景的 root 属性,可通过 show() 方法显示。

该选项卡式 UI 中使用的 FXML

如图 5 所示,search_demo_w_tabs.fxml 文档包含场景图的 XML 表示。继续查看清单 2,我们将介绍本文档中的新概念,而这些概念在第 1 部分并未提及:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.web.*?>
<?import javafx.geometry.*?>
<?import demos.search.*?>

<BorderPane id="app-border" fx:controller="demos.search.SearchDemoController" 
xmlns:fx="http://javafx.com/fxml">
<top>
<HBox>
<children>
<ImageView>
<image>
<Image url="@javafx-logo.png"/>
</image>
</ImageView>
<Region HBox.hgrow="ALWAYS"/>
<Label id="app-title" text="%appTitle"/>
<Region>
<HBox.hgrow>ALWAYS</HBox.hgrow>
</Region>
<HBox>
<children>
<HBox spacing="5" fillHeight="false">
<HBox.margin>
<Insets top="20" right="10"/>
</HBox.margin>
<children>
<TextField fx:id="searchTermTextField" prefColumnCount="11"
promptText="%titleOrArtist" 
onAction="#handleSearchAction"/>
<Button fx:id="searchButton" disable="false" 
onAction="#handleSearchAction"/>
</children>
</HBox>
<Label fx:id="statusLabel"/>
</children>
</HBox>
</children>
</HBox>
</top>

<center>
<TabPane>
<tabs>
<Tab text="%search" closable="false">
<content>
<TableView fx:id="resultsTableView">
<fx:define>
<ResultCellValueFactory fx:id="resultCellValueFactory"/>
</fx:define>
<columns>
<ResultTableColumn key="itemName" text="%name" 
prefWidth="220" 
cellValueFactory="$resultCellValueFactory"/>
<ResultTableColumn key="itemParentName" text="%album" 
prefWidth="220" 
cellValueFactory="$resultCellValueFactory"/>
<ResultTableColumn key="artistName" text="%artist" 
prefWidth="220" 
cellValueFactory="$resultCellValueFactory"/>
</columns>
</TableView>
</content>
</Tab>
<Tab text="%detail" closable="false">
<content>
<VBox fillWidth="false" alignment="topCenter" spacing="6" 
style="-fx-padding: 10 0 0 0">
<children>
<StackPane prefWidth="120" prefHeight="120" 
style="-fx-border-color:#929292; -fx-border-width:1px">
<children>
<ImageView fx:id="artworkImageView"/>
</children>
</StackPane>
<Button fx:id="previewButton" text="%preview" 
onAction="#handlePreviewAction"/>
</children>
</VBox>
</content>
</Tab>
</tabs>
</TabPane>
</center>
<bottom>
<HBox id="footer">
<children>
<Label fx:id="statusLabel"/>  
</children> 
</HBox>  
</bottom>
</BorderPane>

清单 2:search_demo_w_tabs.fxml

访问与 FXML 文档相关的资源

如以下代码段(节选自清单 2)所示,您可以使用 @后缀从与 FXML 文件相关的位置检索资源(例如,PNG 文件中的图像):

<ImageView>
<image>
<Image url="@javafx-logo.png"/>
</image>
</ImageView>

请注意,此处使用的技术与本示例中其他 ImageView 实例所使用的技术不同,因为在那些情况下,Image 对象由控制器来管理。

设置静态属性

您可能已经注意到,如果您调整 SearchDemoFXML 应用程序(加载 search_demo_w_tabs.fxml 之后)的大小,应用程序标题会在标题中保持水平居中。这是由于清单 3(清单 2 中的代码段)所示的 Region 实例起到了水平“弹簧”的作用:

<HBox>
<children>
<ImageView>
<image>
<Image url="@javafx-logo.png"/>
</image>
</ImageView>
<Region HBox.hgrow="ALWAYS"/>
<Label id="app-title" text="%appTitle"/>
<Region>
<HBox.hgrow>ALWAYS</HBox.hgrow>
</Region>
<HBox>
<children>
<HBox spacing="5" fillHeight="false">
<HBox.margin>
<Insets top="20" right="10"/>
</HBox.margin>
<children>
<TextField fx:id="searchTermTextField" prefColumnCount="11"
promptText="%titleOrArtist" 
onAction="#handleSearchAction"/>
<Button fx:id="searchButton" disable="false" 
onAction="#handleSearchAction"/>
</children>
</HBox>
<Label fx:id="statusLabel"/>
</children>
</HBox>
</children>
</HBox>

清单 3:Region 实例的作用就像弹簧

清单 3 中使用的 HBox.hgrow 表示静态属性,等同于以下 Java 代码(这些 Java 代码不是本应用程序示例的组成部分):

    HBox.setHgrow(fooRegion, Priority.ALWAYS);

请注意,清单 3 中的 HBox.margin 同样使用了这一技术来设置 HBox 的边距,以容纳 TextFieldButton。以下提供了等效的 Java 代码供您参考:

    HBox.setMargin(fooHBox, new Insets(20, 10, 0, 0));

利用 JavaFX 级联样式表

如下列代码段(节选自清单 2)所示,FXML 元素可以在与场景相关的级联样式表中引用规则:

        <Label id="app-title" text="%appTitle"/>

在本例中,清单 4 中 #app-title 选择器表达的样式将应用于上述代码段中的 Label。请注意,使用 id 将元素与 CSS 规则相关联以及使用 fx:id 将元素映射到控制器中的实例变量,两者之间存在区别。

#app-border {
-fx-border-color:grey;
-fx-border-width: 3;
}

#footer {
-fx-border-color:grey;
-fx-border-width: 1;
-fx-padding: 5;
}

#app-title {
-fx-font-size:20pt;
-fx-font-weight:normal;
-fx-text-fill:grey;
-fx-padding: 15 0 0 0;
}

清单 4:myStyles.css

此外,正如本系列第 1 部分所述,FXMLLoader.load() 方法加载的 ResourceBundle 将会检索 Label 的文本,如清单 1 所示。例如,当区域设置为 pt_BR 时,Label 将显示从 search_demo_pt_BR.properties 文件检索的字符串“Demo de Busca”,如清单 5 所示。

name=Nome
album=Álbum
artist=Artista
preview=Pré-visualização
searching=Buscando...
aborting=Abortando...
cancelled=Solicitação cancelada
resultCountFormat=Encontrados %1$d itens que correspondem a sua busca
appTitle=Demo de Busca
search=Busca
detail=Detalhe
titleOrArtist=Título ou Artista

清单 5:search_demo_pt_BR.properties

使用 SearchDemoFXML 示例作为小程序并通过 Java Web 启动

按照 Oracle“部署 JavaFX 应用程序”指南中的说明,SearchDemoFXML 示例已被部署为小程序并通过网页上的 Java Web Start 启动,如本文末尾的“另请参见”一节所述。“另请参见”一节还提供了“部署 JavaFX 应用程序”指南的链接。图 7 包含该网页的屏幕截图,其中显示了两个并行运行的应用程序实例,两者加载了不同的 FXML 文档。

此外,图 7 中的网页还显示了几个调用 SearchDemoFXML 应用程序的 Java Web Start 链接,每个链接所调用的应用程序均采用不同的区域设置。小程序和 Java Web Start 链接部署采用了之前 HTML 页面中讨论的命名参数和 Java Web Start 的 JNLP 文件中讨论的命名参数。

图 7:作为小程序和作为本地化 Java Web Start 链接运行的 SearchDemoFXML

总结

FXML 是 JavaFX 2.0 附带的一种工具,该工具支持使用 XML 呈现 UI。除了本系列第一部分中介绍的概念,FXML 还能够在不修改控制器的情况下彻底更改 UI 。这可以通过加载不同的 FXML 文档、使用 JavaFX 级联样式表和创建本地化资源包来实现。通过在这些特性中使用命名参数,可以在启动时为应用程序提供相关信息。

另请参见

关于作者

Jim Weaver 是一位独立的 Java 和 JavaFX 开发人员、作者和演讲者,积极致力于促进富客户端 Java 和 JavaFX 成为新应用程序开发的首选技术。

Jim 撰写的著作包括《Inside Java》、《Beginning J2EE》、《Pro JavaFX Platform》,后者已更新,涵盖了 JavaFX 2.0。他的专业背景包括 15 年的 EDS 系统架构师,以及同样年数的独立开发人员和软件开发公司老板。Jim 在许多国际软件技术会议上发表过演讲,包括在旧金山和圣保罗 JavaOne 2011 大会上的演讲和在俄勒冈州波特兰市 O’Reilly OSCON/Java 2011 大会上的主题演讲。

Jim 的博客为 http://javafxpert.com,Tweet 为 @javafxpert,电子邮件联系方式为 jim.weaver[at]javafxpert.com。