使用 JavaFX 2.0 布置用户界面

时间:2020-12-29 17:05:29

简介

JavaFX 2.0 是一个用于创建富互联网应用程序 (RIA) 的 API 和运行时。JavaFX 于 2007 年推出,2011 年 10 月发布了 2.0 版本。JavaFX 2.0 的一个优点是可以使用成熟、熟悉的工具用 Java 语言编写代码。本文着重介绍如何使用 JavaFX 2.0 的布局功能使场景中的节点出现在希望它们出现的位置并在调整窗口大小时调整为合适的大小。

JavaFX 带有自己的布局类,如图 1 所示(来自 Amy Fowler 在 JavaOne 2011 的演示文稿),旨在便于对任何类型的平台和任何大小的场景进行用户界面布局。这些类位于javafx.scene.layout 程序包中。本文包含一个示例,该示例使用BorderPaneHBox 类以及其他与布局有关的 JavaFX 类创建一个常用的 UI 布局格式。

使用 JavaFX 2.0 布置用户界面

图 1:JavaFX 布局类

所有布局类负责控制其管理的 子节点的位置(可以选择将节点设置为不受管理)。此外,在特定环境下大多数布局类也可以调整其大小可调 子节点的大小。在第 1 步中,我们将进一步讨论大小可调节点的行为。

表 1 中包含图 1 底部从左到右每个布局类及其与调整其子节点大小有关的行为的简短说明。

表 1:布局类及其子节点大小调整行为

布局类

说明及子节点大小调整行为

AnchorPane

允许将子节点边缘定位到其父节点的边缘。不调整子节点大小。

BorderPane

提供经典顶部、左侧、右侧、底部、中心子节点放置。水平调整位于顶部和底部的子节点的大小,垂直调整位于左侧和右侧的子节点的大小,同时在水平和垂直方向调整位于中心的节点的大小。所有大小调整最大可调整至节点在相关方向上的最大大小。

StackPane

将子节点从背景切换到前景。调整子节点大小以填充父节点大小(直至每个节点的最大宽度和高度)。

HBox

包含单行节点。子节点大小水平调整至其首选宽度,但各子节点可以显式设置为水平增加至其最大宽度。默认情况下,子节点大小垂直调整至其最大高度。

VBox

包含单列节点。子节点大小垂直调整至其首选高度,但各子节点可以显式设置为垂直增加至其最大高度。默认情况下,子节点大小水平调整至其最大宽度。

TilePane

提供换行的水平或垂直均匀的“平铺”流。调整子节点的大小以填充平铺的大小(直至节点的最大宽度和高度)。

FlowPane

提供换行的水平或垂直子节点流。不调整子节点大小。

GridPane

将子节点置于弹性网格中,这非常适合复杂布局。大小调整基于给定行或列中显式设置的约束。

我们稍后将在第 1 步中讨论大小可调节点的行为。

LayoutSansTearsSolution 应用程序概述

为帮助您了解如何在 JavaFX 中布置 UI,下面将使用一个名为 LayoutSansTearsSolution 的示例应用程序。如图 2 所示,该应用程序的 UI 包含一个页眉区、一个选项卡式窗格区和一个页脚区,且如用户所期望的,其外观可随窗口大小的调整而调整。

下一节您将下载的 LayoutSansTearsExercise项目包含此示例应用程序的初始代码。目前该应用程序的运行时外观如图 6 所示。随着本文的进展,您将修改其代码以实现图 2 中所示的 LayoutSansTearsSolution 应用程序布局外观。

使用 JavaFX 2.0 布置用户界面

图 2:LayoutSansTearsSolution 应用程序的屏幕截图

调整 LayoutSansTearsSolution 应用程序窗口的大小时(如图 3 所示),各节点的相对位置随之相应地进行调整。例如,Footer Left 和 Footer Right 标签将彼此靠近,App Title 页眉文本保持水平居中。

使用 JavaFX 2.0 布置用户界面

图 3:窗口大小调整后 LayoutSansTearsSolution 的屏幕截图

此外,窗口中心的选项卡式窗格调整大小以占用所有可用的水平空间,以及页眉和页脚未使用的任何空间。

获取并运行 LayoutSansTearsExercise 项目

  • 下载 NetBeans 项目文件 (Zip),其中包括 LayoutSansTearsExercise 项目和 LayoutSansTearsSolution 项目。
  • LayoutSansTearsExercise 项目解压缩到您选择的目录中。
  • 启动 NetBeans,选择 File -> Open Project
  • 在 Open Project 对话框中,转到所选目录后打开 LayoutSansTearsExercise 项目,如图 4 所示。如果收到一个声明无法找到 jfxrt.jar 文件的消息,单击 Resolve 按钮并转到 JavaFX 2.0 SDK 安装目录下面的 rt/lib 文件夹。

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

使用 JavaFX 2.0 布置用户界面

图 4:在 NetBeans 中打开 LayoutSansTearsExercise 项目

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

使用 JavaFX 2.0 布置用户界面

图 5:在 NetBeans 中运行应用程序

LayoutSansTearsExercise 应用程序应显示在一个窗口中,如图 6 所示。

使用 JavaFX 2.0 布置用户界面

图 6:LayoutSansTearsExercise 应用程序的屏幕截图

注意 App Title 文本、搜索文本区和 Go 按钮在页眉右侧挤作一堆。另外,搜索文本区显示为垂直拉伸,但我们希望其高度只要够输入几行文本就行了。此外,Footer Left 和 Footer Right 标签在页脚左侧挤作一堆。

您的任务是添加一些代码以实现先前所述的布局外观和行为(如图 2 和图 3 所示)。下面我们来讨论可用于实现此外观和行为的步骤。

第 1 步:回顾 LayoutSansTearsExercise 应用程序中所使用的布局策略

LayoutSansTearsExercise 应用程序中所使用的*布局策略是将BorderPane 置于场景根部。将一个布局类或任何其他大小可调类用作场景的根节点时,该节点将随场景调整大小而自动调整。因此,当用户调整应用程序窗口(JavaFX 舞台)大小时,BorderPane 将调整大小以占据窗口内的全部空间。

除了创建 BorderPane 之外,还创建一个HBox 以容纳标题中的节点,并将其置于BorderPane 顶部。然后创建一个TabPane 并将其置于 BorderPane 的中心位置。再创建另一个 HBox 以容纳页脚中的节点,并将其置于 BorderPane 底部。

我们来看一下清单 1 中的代码,显示了示例应用程序的初始代码,位于 LayoutSansTears.java 文件中。

package javafxpert.layoutsanstears.ui;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class LayoutSansTears extends Application {

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

@Override
public void start(Stage primaryStage) {
Region headerLeftSpring = new Region();

// TO DO: Declare a variable of type Region, assigning a
// new Region object to it for use as a "spring" in the
// header between the title of the app and the search box

// TO DO: Declare a variable of type Region, assigning a
// new Region object to it for use as a "spring" in the
// footer between the left and right labels

ImageView logo = new ImageView(
new Image(getClass().getResourceAsStream("images/javafx-logo.png"))
);

HBox searchBox = HBoxBuilder.create()
.spacing(5)
.children(
TextAreaBuilder.create()
.prefWidth(120)
.prefHeight(40)

// TO DO: Use a method of the TextAreaBuilder to set the maximum
// height of the TextArea to its preferred size

.build(),
ButtonBuilder.create()
.text("Go")
.build()
)
.build();

Scene scene = SceneBuilder.create()
.stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
.width(800)
.height(500)
.root(
BorderPaneBuilder.create()
.top(
HBoxBuilder.create()
.children(
logo,
headerLeftSpring,
LabelBuilder.create()
.id("app-title")
.text("App Title")
.build(),

// TO DO: Insert the Region object created to act as a "spring"

searchBox
)
.build()
)
.center(
TabPaneBuilder.create()
.tabs(
TabBuilder.create()
.text("Tab A")
.build(),
TabBuilder.create()
.text("Tab B")
.build(),
TabBuilder.create()
.text("Tab C")
.build()
)
.build()
)
.bottom(
HBoxBuilder.create()
.id("footer")
.children(
new Label("Footer Left"),

// TO DO: Insert the Region object created to act as a "spring"

new Label("Footer Right")
)
.build()
)
.build()
)
.build();

HBox.setHgrow(headerLeftSpring, Priority.ALWAYS);

// TO DO: Use a static method of HBox to allow the headerRightSpring
// to compete for any extra horizontal space

// TO DO: Use a static method of HBox to allow the footerCenterSpring
// to compete for any extra horizontal space

// TO DO: Use a static method of HBox to give the searchBox
// a margin of 20 pixels on the top and 10 pixels on the right

primaryStage.setTitle("Layout Sans Tears: Exercise");
primaryStage.setScene(scene);
primaryStage.show();
}
}

清单 1:LayoutSansTears.java

使用SceneBuilderBorderPaneBuilder

LayoutSansTears.java 中的初始代码利用了 JavaFX 2.0 API 中的构建器 类,包括清单 2 中所示的SceneBuilderBorderPaneBuilder类。这两个类分别创建 SceneBorderPane 的实例。

:代码中有一些“TO DO”注释(将由您稍后在第 2 步中填写),清单 2 中省略了这些注释。

    Scene scene = SceneBuilder.create()
.stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
.width(800)
.height(500)
.root(
BorderPaneBuilder.create()
.top(
HBoxBuilder.create()
.children(
logo,
headerLeftSpring,
LabelBuilder.create()
.id("app-title")
.text("App Title")
.build(),
searchBox
)
.build()
)
.center(
TabPaneBuilder.create()
.tabs(
TabBuilder.create()
.text("Tab A")
.build(),
TabBuilder.create()
.text("Tab B")
.build(),
TabBuilder.create()
.text("Tab C")
.build()
)
.build()
)
.bottom(
HBoxBuilder.create()
.id("footer")
.children(
new Label("Footer Left"),
new Label("Footer Right")
)
.build()
)
.build()
)
.build();

清单 2:创建 SceneBorderPane 的实例

在清单 2 中,使用 HBoxBuilder 类创建一个水平容器,其中放置页眉的节点。这些节点(如清单 1 所示)为:

  • JavaFX 徽标,即清单 1 中所示的 ImageView
  • 一个“弹簧”节点,稍后在第 2 步中详述
  • 应用程序标题
  • 一个搜索框,包括清单 1 中所示的 TextAreaButton

清单 2 中还使用 HBoxBuilder 类创建另一个水平容器,在其中放置页脚的 Footer Left 和 Footer Right 标签。

JavaFX API 提供了许多构建器类,旨在支持以声明式编程风格来创建对象和设置对象属性。例如,正如您刚刚体验到的,清单 2 中的 SceneBuilder 类创建 Scene 类的一个实例,并以所需场景宽度和高度之类的属性来填充该实例。正如您在清单 1 中所见,此应用程序还使用了其他构建器类,它们是:HBoxBuilderTextAreaBuilderButtonBuilderBorderPaneBuilderLabelBuilderTabPaneBuilderTabBuilder

:尽管在本示例中使用了构建器类,但也可以不使用它们,以一种更为过程性的风格或以 FXML 表达的方式来编写该应用程序。本文结尾处的“另请参见”部分包含一个链接,您可以访问该链接以了解 FXML。

了解大小可调节点的行为

LayoutSansTears.java 中的所有节点均可通过父布局容器调整大小。当节点大小可调整时,其父布局容器将在布局过程中调整其大小,通常是调整到其首选大小。出于同样的原因,应用程序从不直接设置大小可调节点的大小。除了TextImageViewGroup 以及Shape 的子类之外,JavaFX 中所有Node 子类均可通过父布局容器调整大小。为了更改这些非大小可调 类的大小,应用程序必须直接设置大小(Group 除外,其大小采用其子节点的集合边界)。

您已了解了表 1 中所描述的大小可调节点的行为和布局行为,下面依照您的理解我们来看看 LayoutSansTears.java 应用程序(在 LayoutSansTearsExercise 项目中)的行为。

例如,当用户垂直调整舞台大小时,BorderPane 的大小也会跟着调整,因为我们已将其分配给场景的根。当BorderPane 垂直调整大小时,它不会试图垂直调整其顶部和底部节点的大小,因而这些节点会保持其首选高度。因此,位于BorderPane 中心的节点(即TabPane)的大小最高调整到其最大高度。您可能要问,TabPane 可以垂直增加多少?或者,换种说法,TabPane 的最大高度是多少?要回答这个问题,需要了解有界 节点与* 节点,下面我们讨论这两种节点。

了解有界节点与*节点

有界 节点是这样的大小可调节点,其最大高度与首选高度相同,其最大宽度与首选宽度相同。例如,如表 1 所述,当 HBox 尝试调整子节点的高度时,它最多只能将其调整到最大高度。因此,有界节点最多只能调整到其首选宽度和高度。

* 节点是这样的大小可调节点,其 maxWidth() maxHeight()返回 Double.MAX_VALUE。因此,布局容器对*节点进行大小调整时,没有宽度和高度的限制。

大小可调节点可以在一个方向有界而在另一个方向上*。例如,默认情况下,MenuBar 的宽度无限制,而高度受限制。这使 MenuBar 可以按布局容器所需要的宽度调整水平大小,同时保持其首选高度。表 2 包含大小可调类的默认有界和*特征。

表 2:大小可调类及其默认有界/*特征

有界

*

水平*

垂直*

Button

Region(所有布局的超类)

MenuBar

Separator(垂直)

Label

ListView

ToolBar

ScrollBar(垂直)

ChoiceBox

TreeView

Separator(水平)

 

Hyperlink

TableView

ScrollBar(水平)

 

ProgressBar

TabPane

TextField

 

Slider

SplitPane

   
 

ScrollPane

   
 

TextArea

   

对于前面的问题“TabPane 的最大高度是多少?”,答案是其最大高度是*的。下面花一点时间再做一些调整应用程序窗口大小的实验,验证图 6 所示的节点位置和大小是否与您的理解一致。

例如,UI 右上侧中 TextArea 的首选高度设置为 40 像素 ,而其外观比这高得多。这是因为TextArea 在默认情况下是*的,因此清单 3 中的HBoxTextArea 的大小调整为 HBox 的高度:

    Scene scene = SceneBuilder.create()
.stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
.width(800)
.height(500)
.root(
BorderPaneBuilder.create()
.top(
HBoxBuilder.create()
.children(
logo,
headerLeftSpring,
LabelBuilder.create()
.id("app-title")
.text("App Title")
.build(),
searchBox
)
.build()
)
.center(
TabPaneBuilder.create()
.tabs(
TabBuilder.create()
.text("Tab A")
.build(),
TabBuilder.create()
.text("Tab B")
.build(),
TabBuilder.create()
.text("Tab C")
.build()
)
.build()
)
.bottom(
HBoxBuilder.create()
.id("footer")
.children(
new Label("Footer Left"),
new Label("Footer Right")
)
.build()
)
.build()
)
.build();

清单 3:将 TextArea 的大小调整为HBox 的高度

正如清单 3 中所提示,我们将让您来完成“TO DO”注释的工作。目标是将布局从图 6 所示的 LayoutSansTearsExercise 外观微调为图 3 所示的 LayoutSansTearsSolution 外观。

第 2 步:使用默认最大大小覆盖、增长限制和边距对布局进行微调

在这一步中,您将使用三种方法来对示例应用程序的布局进行微调:

  • 覆盖默认最大大小值
  • HBox 的各子节点设置水平增长限制
  • 设置围绕布局容器中各个子节点的边距

覆盖大小可调节点的默认最大大小

为使清单 3 中 TextArea 的高度成为有界的式而不是*的,需要将其最大高度设置为其首选高度。为此,推荐使用Region 类中定义的一个名为 USE_PREF_SIZE 的常量。

为在示例中实现这一点,我们继续在清单 3 所示的位置向 TextAreaBuilder 添加以下方法调用:

          .maxHeight(Region.USE_PREF_SIZE)

运行应用程序时,TextArea 现在应显示为其首选高度,如先前图 2 中所示。

如果您以后想将 TextArea(或任何其他大小可调节点)的最大高度重置为其默认值,可以将Region.USE_COMPUTED_SIZE 常量作为参数传递给TextAreasetMaxHeight() 方法。

表 3 包含大小可调节点的一些方法,这些方法可用于使该节点的宽度、高度或两者有界、*或重置。

表 3:使大小可调节点有界、*或重置为其默认值

 

有界

*

重置为默认值

setMaxWidth()

Region.USE_PREF_SIZE

Double.MAX_VALUE

Region.USE_COMPUTED_SIZE

setMaxHeight()

Region.USE_PREF_SIZE

Double.MAX_VALUE

Region.USE_COMPUTED_SIZE

setMaxSize()

Region.USE_PREF_SIZE, Region.USE_PREF_SIZE

Double.MAX_VALUE, Double.MAX_VALUE

Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE

使 TextArea 在 HBox 中显示为其首选高度的另一种办法是将HBoxfillHeight 属性设置为 false。这允许所有子节点显示为其首选高度。这种方法适用于本示例程序,但要注意,这将应用于HBox 中的所有 子节点,而不是像HBox.maxHeight() 方法那样应用于单个节点。

现在解决了图 6 中所示 TextArea 的高度问题,下面我们来解决页眉中节点在右侧挤作一堆的问题。

HBox 的各子节点设置水平增长限制

清单 4 中的代码实现了图 6 所示的页眉区。

    Scene scene = SceneBuilder.create()
.stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
.width(800)
.height(500)
.root(
BorderPaneBuilder.create()
.top(
HBoxBuilder.create()
.children(
logo,
headerLeftSpring,
LabelBuilder.create()
.id("app-title")
.text("App Title")
.build(),
searchBox
)
.build()
)
.center(
TabPaneBuilder.create()
.tabs(
TabBuilder.create()
.text("Tab A")
.build(),
TabBuilder.create()
.text("Tab B")
.build(),
TabBuilder.create()
.text("Tab C")
.build()
)
.build()
)
.bottom(
HBoxBuilder.create()
.id("footer")
.children(
new Label("Footer Left"),
new Label("Footer Right")
)
.build()
)
.build()
)
.build();

清单 4:实现页眉区

看起来似乎我们又要让您干活了,这次是在 HBox 中插入一个水平“弹簧”,以便更多子节点可以散布开来。如清单 4 所示,我们已在徽标和标签之间插入了这样的一个弹簧,这就是为什么它们两个能够散开(如图 6 所示)的原因。

为实现该弹簧,我们首先声明一个名为 headerLeftSpringRegion 类型的变量,并为其分配一个新的Region 对象。如清单 4 所示,为使headerLeftSpring 节点能够在HBox 中水平增长,我们使用了 HBox 的静态 setHgrow() 方法。传递Priority.ALWAYS 参数意味着我们希望该节点占用所有可用的水平空间,并与水平增长限制为ALWAYS 的其他节点共享此空间。Priority 枚举中的其他常量为SOMETIMESNEVER,可用于进一步控制增长行为。

继续通过在先前清单所指示的位置插入代码,在应用程序标题和搜索框之间实现一个弹簧。然后运行应用程序,可以看到 App Title 标签在页眉中水平居中,这是因为这两个弹簧共享了所有可用水平空间。应用程序的外观应类似于图 7。

使用 JavaFX 2.0 布置用户界面

图 7:第 2 步部分完成的 LayoutSansTearsExercise

顺便说一句,您可以使用上述方法,通过使用 VBox 的静态setVgrow() 方法将一个垂直弹簧插入VBox。此外,GridPane 类也有用于GridPane 布局的静态 setHgrow()setVgrow() 方法。

现在您已解决了图 6 所示标题中的水平空白间隔问题,下面我们来为搜索框设置边距,以便其不会触到右上角。

设置围绕布局容器中各个子节点的边距

清单 5 中的代码实现了图 6 所示的搜索框。

    HBox searchBox = HBoxBuilder.create()
.spacing(5)
.children(
TextAreaBuilder.create()
.prefWidth(120)
.prefHeight(40)
.build(),
ButtonBuilder.create()
.text("Go")
.build()
)
.build();

...code omitted...

HBoxBuilder.create()
.children(
logo,
headerLeftSpring,
LabelBuilder.create()
.id("app-title")
.text("App Title")
.build(),
searchBox
)
.build()

...code omitted...

// TO DO: Use a static method of HBox to give the searchBox
// a margin of 20 pixels on the top and 10 pixels on the right

清单 5:设置边距

有几种办法可以在搜索框中实现我们所需要的顶部 20 个像素、右侧 10 个像素的填充空白。在我们的示例应用程序中的实现办法是为创建搜索框的 HBoxBuilder 添加 padding() 方法调用,如以下代码段所示:

    HBox searchBox = HBoxBuilder.create()
.padding(new Insets(20, 10, 0, 0))
...code omitted...
.build()

另一个适用范围更广泛的围绕布局容器的子节点实现边距的办法是使用其布局类的静态 setMargin() 方法。为在示例中实现这一点,我们在清单 5 所示位置添加以下代码行。

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

运行该应用程序时,搜索框现在应距顶部 20 个像素、距右侧 10 个像素,如先前图 2 中所示。

现在您已解决了标题中的所有空白间隔问题,下面我们将利用 JavaFX CSS 使页脚的外观如图 2 所示。

第 3 步:利用 JavaFX CSS 修改布局

JavaFX 一个非常强大的方面是能够使用 CSS 在场景中动态设置节点(包括布局容器节点)样式。图 7 中最新显示的页脚区由清单 6 所示代码(来自清单 1 的代码段)以及清单 7 所示 CSS 文档来实现。

    Scene scene = SceneBuilder.create()
.stylesheets("javafxpert/layoutsanstears/ui/myStyles.css")
...code omitted...

HBoxBuilder.create()
.id("footer")
.children(
new Label("Footer Left"),

// TO DO: Insert the Region object created to act as a "spring"

new Label("Footer Right")
)
.build()

清单 6:来自清单 1 的代码段

/*
* myStyles.css - style sheet for LayoutSansTears.java JavaFX layout example
*/

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

/* TO DO: Insert a property and value that will give the interior of
the layout container a padding of 5 pixels on every side */

}

#app-title {
-fx-font-size: 40pt;
-fx-font-weight: normal;
-fx-text-fill: grey;
}

清单 7:myStyles.css

使用 CSS 样式表属性修改布局

如清单 6 所示,我们通过使用 SceneBuilder 类的stylesheets() 方法将一个样式表与 JavaFX 应用程序关联。

包含页脚中标签的 HBox 通过使用HBoxBuilder 中的 id() 方法来利用清单 7 中的 #footer 选择器的样式属性和值。因此,HBox 将使用#footer 选择器中指定的边框颜色和宽度呈现出来,如图 7 中最新所示。

如前所述,有多种方式可以设置围绕布局容器中子节点的边距。这里我们对页脚使用的方法是,通过在清单 7 中 #footer 选择器的标出位置处添加以下代码行来修改样式表:

  -fx-padding: 5;

–fx-padding 属性导致布局容器内部在其顶部、右侧、底部和左侧内部填充 5 个像素的空白。或者,如果您希望这些填充空白值彼此不同,可以提供四个以空格分隔的值。

继续实现填充空白修改,完成后,注意还有一件事未完成:在页脚中的两个标签之间实现一个弹簧。您只需按照与在标题中实现弹簧时相同的方法来做。

运行 LayoutSansTearsExercise 项目现在应产生与图 2 中所示运行LayoutSansTearsSolution 项目相同的外观。

总结

JavaFX 有一些非常强大的特性可用于布局用户界面,其中一些我们已在本文讨论并在 LayoutSansTearsSolution 应用程序中进行了演示。利用这些特性,您可以让应用程序按照您希望的方式显示,无论场景大小或平台类型如何。了解每种类型布局类的行为以及有界节点与*节点等概念将对您大有助益,可帮助您让 UI 完全按照您所希望的方式显示。