JavaFX17-学习手册-六-

51 阅读1小时+

JavaFX17 学习手册(六)

原文:Learn JavaFX 17

协议:CC BY-NC-SA 4.0

十一、模型-视图-控制器模式

在本章中,您将学习:

  • 什么是模型-视图-控制器模式

  • 模型-视图-控制器模式的其他变体是什么,比如模型-视图-展示者模式

  • 如何使用模型-视图-演示者模式开发 JavaFX 应用程序

本章的例子在com.jdojo.mvc包中。为了让它们工作,您必须在module-info.java文件中添加相应的行:

...
opens com.jdojo.mvc to javafx.graphics, javafx.base;
opens com.jdojo.mvc.model to javafx.graphics, javafx.base;
opens com.jdojo.mvc.view to javafx.graphics, javafx.base;
...

什么是模型-视图-控制器模式?

JavaFX 允许您使用 GUI 组件创建应用程序。GUI 应用程序执行三项任务:接受用户输入、处理输入和显示输出。GUI 应用程序包含两种类型的代码:

  • 处理特定于领域的数据和业务规则的领域代码

  • 处理操作用户界面小部件的表示代码

通常要求特定领域中的相同数据以不同的形式呈现。例如,您可能有一个使用 HTML 的 web 界面和一个使用 JavaFX 的桌面界面来呈现相同的数据。为了便于维护应用程序代码,通常需要将应用程序分成两个逻辑模块,其中一个模块包含表示代码,另一个包含领域代码(特定于领域的业务逻辑和数据)。这种划分是以这样一种方式进行的,即表示模块可以看到域模块,但反之则不能。这种类型的划分支持具有相同域代码的多个表示。

模型-视图-控制器(model-view-controller,MVC)模式是最古老也是最流行的模式,用于为 GUI 应用程序建模,以促进这种划分。MVC 模式由三部分组成:模型视图控制器。图 11-1 显示了 MVC 组件以及它们之间的交互的图形视图。

img/336502_2_En_11_Fig1_HTML.png

图 11-1

经典 MVC 模式中参与者之间的交互

在 MVC 中,模型由模拟现实世界问题的领域对象组成。视图控制器由处理表示的表示对象组成,比如输入、输出和用户与 GUI 元素的交互。控制器接受用户的输入并决定如何处理。也就是说,用户直接与控制器交互。视图在屏幕上显示输出。每个视图都与一个唯一的控制器相关联,反之亦然。屏幕上的每个小部件都是一个视图,都有相应的控制器。因此,在 GUI 屏幕中通常有多个视图-控制器对。该模型不知道任何特定的视图和控制器。但是,视图和控制器是特定于模型的。控制器命令模型修改其状态。视图和模型总是保持同步。模型通知视图其状态的变化,因此视图可以显示更新的数据。通过一个观察者模式来促进模型到视图的交互。请记住,模型完全不知道任何特定的视图。该模型为视图提供了一种订阅其状态更改通知的方式。任何感兴趣的视图都可以订阅模型来接收状态更改通知。每当模型的状态改变时,模型通知所有已经订阅的视图。

到目前为止,关于 MVC 模式的描述是 MVC 的原始概念,它被用于在 1980 年创建的 Smalltalk-80 语言中开发用户界面。Smalltalk 有许多变体。MVC 中的概念,即 GUI 应用程序中的表示和域逻辑应该分离,仍然适用。然而,在 MVC 中,在三个组件之间划分职责存在问题。例如,哪个组件具有更新视图属性的逻辑,比如改变视图颜色或禁用它,这取决于模型的状态?视图可以有自己的状态。显示项目列表的列表包含当前选定项目的索引。选定的索引是视图的状态,而不是模型。一个模型可能同时与几个视图相关联,并且存储所有视图的状态不是模型的责任。

MVC 中哪个组件负责存储视图逻辑和状态的问题导致了 MVC 的另一个变种,称为应用程序模型 MVC (AM-MVC)。在 AM-MVC 中,在模型和视图/控制器之间引入了一个名为应用模型的新组件。它的目的是包含表示逻辑和状态,从而解决在原始 MVC 中哪个组件保持表示逻辑和状态的问题。MVC 中的模型与视图是解耦的,在 AM-MVC 中也是如此。两者都使用相同的观察者技术来保持视图和模型的同步。在 AM-MVC 中,应用程序模型应该保存视图相关的逻辑,但是不允许直接访问视图。当应用程序模型必须更新视图属性时,这会导致庞大而丑陋的代码。图 11-2 显示了 AM-MVC 组件以及它们之间的交互的图形视图。

img/336502_2_En_11_Fig2_HTML.png

图 11-2

AM-MVC 模式中参与者之间的交互

后来,像微软 Windows 和 Mac OS 这样的现代图形操作系统提供了本地小部件,用户可以直接与之交互。这些小部件将视图和控制器的功能合二为一。这导致了 MVC 的另一种变体,称为模型-视图-展示者(MVP)模式。现代小部件还支持数据绑定,这有助于用更少的代码行保持视图和模型同步。图 11-3 显示了 MVP 组件以及它们之间的交互的图示。

img/336502_2_En_11_Fig3_HTML.png

图 11-3

MVP 模式中参与者之间的交互

在 MVC 中,屏幕上的每个小部件都是一个视图,它有自己独特的控制器。在 MVP 中,视图由几个小部件组成。视图截取来自用户的输入,并将控制权交给演示者。请注意,视图不会对用户输入做出反应。它只会拦截它们。视图还负责显示来自模型的数据。

视图向演示者通知用户输入。它决定了如何对用户的输入做出反应。演示者负责演示逻辑,操作视图,并向模型发出命令。一旦演示者修改了模型,视图就会使用观察者模式进行更新,就像在 MVC 中一样。

模型负责存储特定领域的数据和逻辑。像 MVC 一样,它独立于任何视图和演示者。演示者命令模型改变,当视图从模型接收到状态改变的通知时,视图更新自己。

MVP 也有一些变种。他们在视图和演示者的责任上有所不同。在一个变体中,视图负责所有视图相关的逻辑,而不需要演示者的帮助。在另一个变体中,视图负责所有可以以声明方式处理的简单逻辑,除了当逻辑复杂时,由呈现者处理。在另一个变体中,展示者处理所有与视图相关的逻辑并操纵视图。这种变体被称为被动视图 MVP ,其中视图不知道模型。图 11-4 显示了 MVP 被动视图中的组件以及它们之间的交互。

img/336502_2_En_11_Fig4_HTML.png

图 11-4

被动视图 MVP 模式中参与者之间的交互

MVC 的概念,即表示逻辑应该从领域逻辑中分离出来,已经存在了 30 多年了,并且将以这样或那样的形式存在。MVC 的所有变体都试图实现与经典 MVC 相同的功能,尽管方式不同。这些变体在组件的职责上不同于传统的 MVC。当有人在 GUI 应用程序设计中谈论 MVC 时,请确保您理解使用了 MVC 的哪种变体,以及哪些组件执行哪些任务。

模型-视图-演示者示例

本节给出了一个使用 MVP 模式的详细例子。

要求

对于这里的例子,您将开发一个 GUI 应用程序,让用户输入一个人的详细信息,验证数据,并保存它。该表格应包含

  • 人员 ID 字段:自动生成的唯一不可编辑字段

  • 名字字段:一个可编辑的文本字段

  • 姓氏字段:可编辑的文本字段

  • 出生日期:可编辑的文本字段

  • 年龄类别:基于出生日期的自动计算的不可编辑字段

  • 保存按钮:保存数据的按钮

  • 关闭按钮:关闭窗口的按钮

应根据以下规则验证个人数据:

  • 名字和姓氏必须至少有一个字符长。

  • 如果输入了出生日期,它不能是将来的日期。

设计

三个类别将代表 MVP 的三个组成部分:

  • Person阶级

  • PersonViewPersonPresenter

Person类代表模型,PersonView类代表视图,PersonPresenter类代表演示者。按照 MVP 模式的要求,Person类对于PersonViewPersonPresenter类是不可知的。PersonViewPersonPresenter类将相互交互,它们将直接使用Person类。

让我们通过将与模型和视图相关的类放在不同的 Java 包中来对它们进行逻辑划分。com.jdojo.mvc.model包将包含与模型相关的类,com.jdojo.mvc.view包将包含与视图相关的类。图 11-5 显示完成的窗口。

img/336502_2_En_11_Fig5_HTML.jpg

图 11-5

人员管理窗口的初始屏幕截图

实施

以下段落描述了 MVP 示例应用程序的三个层的实现。

模型

清单 11-1 包含了Person类的完整代码。Person类包含领域数据和业务规则的代码。在现实生活中,您可能希望将这两者分成多个类。但是,对于像这样的小应用程序,让我们将它们放在一个类中。

// Person.java
// ...find in the book's download area.

Listing 11-1 The Person Class Used As the Model

Person类声明了一个AgeCategory枚举来表示不同的年龄:

public enum AgeCategory {BABY, CHILD, TEEN, ADULT, SENIOR, UNKNOWN};

个人 ID、名字、姓氏和出生日期由 JavaFX 属性表示。personId属性被声明为只读,并且是自动生成的。为这些属性提供了相关的 setter 和 getter 方法。

包含了isValidBirthDate()isValidPerson()方法来执行特定于域的验证。getAgeCategory()方法属于Person类,因为它根据出生日期计算一个人的年龄类别。我编了一些日期范围,把一个人的年龄分成不同的类别。您可能想将这个方法添加到视图中。但是,您需要为每个视图复制这个方法中的逻辑。该方法使用模型数据并计算一个值。它对视图一无所知,所以它属于模型,而不属于视图。

save()方法保存个人数据。保存方法很简单;如果个人数据有效,它只是在标准输出上显示一条消息。在实际应用中,它会将数据保存到数据库或文件中。

景色

清单 11-2 中显示的PersonView类表示这个应用程序中的视图。它主要负责显示模型中的数据。

// PersonView.java
// ...find in the book's download area.

Listing 11-2 The PersonView Class Used As the View

PersonView类继承自GridPane类。它包含每个 UI 组件的一个实例变量。它的构造器将模型(Person类的一个实例)和日期格式作为参数。日期格式是用于显示出生日期的格式。请注意,出生日期的格式是特定于视图的,因此它应该是视图的一部分。模型不知道视图显示出生日期的格式。

initFieldData()方法用数据初始化视图。我使用 JavaFX 绑定将 UI 节点中的数据绑定到模型数据,除了出生日期和年龄类别字段。此方法将出生日期和年龄类别字段与模型同步。layoutForm()方法在网格窗格中布置 UI 节点。bindFieldsToModel()方法将人员 ID、名字和姓氏TextField绑定到模型中相应的数据字段,因此它们保持同步。syncBirthDate()方法从模型中读取出生日期,对其进行格式化,并显示在视图中。syncAgeCategory()方法同步年龄类别字段,该字段由模型根据出生日期计算得出。

请注意,视图,PersonView类不知道演示者,PersonPresenter类。那么视图和演示者将如何交流呢?演示者的角色主要是从视图中获取用户的输入,并根据这些输入采取行动。演示者将拥有对视图的引用。它将向视图添加事件侦听器,因此当视图中的数据发生变化时,它会得到通知。在事件处理程序中,演示者控制并处理输入。如果应用程序需要引用视图中的演示者,您可以将其作为视图类的构造器的参数。或者,您可以在视图类中提供一个 setter 方法来设置演示者。

演示者

清单 11-3 中显示的PersonPresenter类表示这个应用程序中的演示者。它主要负责截取视图中的新输入并进行处理。它直接与模型和视图通信。

// PersonPresenter.java
// ...find in the book's download area.

Listing 11-3 The PersonPresenter Class Used As the Presenter

PersonPresenter类的构造器将模型和视图作为参数。attachEvents()方法将事件处理程序附加到视图的 UI 组件上。在这个例子中,您对截取视图中的所有输入不感兴趣。但是您对出生日期的更改以及点击保存和关闭按钮感兴趣。您不想检测出生日期字段中的所有编辑更改。如果您对出生日期字段中的所有更改感兴趣,您需要为它的text属性添加一个更改监听器。您希望仅在用户完成输入出生日期时检测更改。为此

  • 您将焦点监听器附加到场景,并检测出生日期是否已失去焦点。

  • 您将一个动作侦听器附加到出生日期字段,以便在字段获得焦点时拦截 Enter 键的按下。

每当出生日期字段失去焦点或焦点仍在字段中时按下 Enter 键,这将验证并刷新出生日期和年龄类别。

handleBirthDateChange()方法处理出生日期字段的变化。它在更新模型之前验证出生日期格式。如果出生日期无效,它会向用户显示一条错误消息。最后,它告诉视图更新出生日期和年龄类别。

当用户点击 Save 按钮时,调用saveData()方法,它命令模型保存数据。showError()方法不属于演示者。这里,您添加了它,而不是创建一个新的视图类。它用于显示错误消息。

把它们放在一起

让我们将模型、视图和演示者放在一起,在应用程序中使用它们。清单 11-4 中的程序创建模型、视图和展示者,将它们粘合在一起,并在如图 11-5 所示的窗口中显示视图。请注意,在创建演示者之前,必须将视图附加到场景。这是必需的,因为演示者将焦点改变监听器附加到场景。在将视图添加到场景之前创建演示者将导致一个NullPointerException

// PersonApp.java
// ...find in the book's download area.

Listing 11-4 The PersonApp Class Uses the Model, View, and Presenter to Create a GUI Application

摘要

通常要求相同的领域数据以不同的形式呈现。例如,您可能有一个使用 HTML 的 web 界面和一个使用 JavaFX 的桌面界面来呈现相同的数据。为了便于维护应用程序代码,通常需要将应用程序分成两个逻辑模块,其中一个模块包含表示代码,另一个包含领域代码(特定于领域的业务逻辑和数据)。这种划分是以这样一种方式进行的,即表示模块可以看到域模块,但反之则不能。这种类型的划分支持具有相同域代码的多个表示。MVC 模式是最古老也是最流行的模式,它为 GUI 应用程序建模以促进这种划分。MVC 模式由三个组件组成:模型、视图和控制器。

在 MVC 中,模型由模拟现实世界问题的领域对象组成。视图和控制器由处理表示的表示对象组成,例如输入、输出和用户与 GUI 元素的交互。控制器接受来自用户的输入并决定如何处理它们。也就是说,用户直接与控制器交互。视图在屏幕上显示输出。每个视图都与一个唯一的控制器相关联,反之亦然。屏幕上的每个小部件都是一个视图,都有相应的控制器。在 MVC 中,在三个组件之间划分职责产生了问题。例如,哪个组件具有更新视图属性的逻辑,比如改变视图颜色或禁用它,这取决于模型的状态?

MVC 中哪个组件负责存储视图逻辑和状态的问题导致了 MVC 的另一种变体,称为应用程序模型 MVC。在 AM-MVC 中,在模型和视图/控制器之间引入了一个新的组件,称为应用程序模型。它的目的是包含表示逻辑和状态,从而解决哪个组件在原始 MVC 中保持表示逻辑和状态的问题。

后来,像微软 Windows 和 Mac OS 这样的现代图形操作系统提供了本地小部件,用户可以直接与之交互。这些小部件将视图和控制器的功能合二为一。这导致了 MVC 的另一种变体,称为模型-视图-呈现者模式。

在 MVC 中,屏幕上的每个小部件都是一个视图,它有自己独特的控制器。在 MVP 中,视图由几个小部件组成。视图截取来自用户的输入,并将控制权交给演示者。请注意,视图不会对用户的输入做出反应;它只会拦截它们。视图通知演示者用户的输入,并决定如何对其做出反应。演示者负责演示逻辑,操作视图,并向模型发出命令。一旦演示者修改了模型,视图就会使用观察者模式进行更新,就像在 MVC 中一样。

MVP 也有一些变种。他们在视图和演示者的责任上有所不同。在一个变体中,视图负责所有视图相关的逻辑,而不需要演示者的帮助。在另一个变体中,视图负责所有可以以声明方式处理的简单逻辑,除了当逻辑复杂时,由呈现者处理。在另一个变体中,展示者处理所有与视图相关的逻辑并操纵视图。这种变体被称为被动视图 MVP,其中视图不知道模型。

下一章将向您介绍用于在 JavaFX 应用程序中构建视图的控件。

十二、了解控件

在本章中,您将学习:

  • Java 中的控件是什么

  • 关于实例表示 JavaFX 中控件的类

  • 关于LabelButtonCheckBoxRadioButtonHyperlinkChoiceBoxComboBoxListViewColorPickerDatePickerTextFieldTextAreaMenu等控件

  • 如何使用 CSS 设计控件的样式

  • 如何使用FileChooserDirectoryChooser对话框

本章的例子在com.jdojo.control包中。为了让它们工作,您必须在module-info.java文件中添加相应的一行:

...
opens com.jdojo.control to javafx.graphics, javafx.base;
...

JavaFX 中有很多控件,关于控件有很多要说的。出于这个原因,控件的示例代码只是以一种简化的方式呈现。要获得完整的列表,请查阅该书的下载区。

什么是控件?

JavaFX 允许您使用 GUI 组件创建应用程序。具有 GUI 的应用程序执行三项任务:

  • 接受用户通过键盘或鼠标等输入设备的输入

  • 处理输入(或根据输入采取行动)

  • 显示输出

UI 提供了一种在应用程序及其用户之间交换输入和输出信息的方式。使用键盘输入文本、使用鼠标选择菜单项、点击按钮或其他动作都是向 GUI 应用程序提供输入的示例。该应用程序使用文本、图表、对话框等在计算机显示器上显示输出。

用户使用称为控件小部件的图形元素与 GUI 应用程序进行交互。按钮、标签、文本字段、文本区域、单选按钮和复选框是控件的几个例子。键盘、鼠标和触摸屏等设备用于向控件提供输入。控件还可以向用户显示输出。控件生成指示用户和控件之间发生某种交互的事件。例如,使用鼠标或空格键按下按钮会生成一个动作事件,指示用户已经按下了该按钮。

JavaFX 提供了一组丰富的易于使用的控件。控件被添加到布局窗格中,对它们进行定位和调整大小。第十章讨论了布局窗格。本章讨论如何使用 JavaFX 中可用的控件。

通常,MVP 模式(在第十一章中讨论)用于在 JavaFX 中开发 GUI 应用程序。MVP 要求你至少有三个类,并以某种方式将你的业务逻辑放在某些类中。一般来说,这会使应用程序代码膨胀,尽管这样做是有道理的。本章将关注不同类型的控件,而不是学习 MVP 模式。您将把 MVP 模式所需的类嵌入到一个类中,以保持代码简洁并节省本书的大量空间!

了解控件类层次结构

JavaFX 中的每个控件都由一个类的实例来表示。如果多个控件共享基本功能,则它们从一个公共基类继承。控制类包含在javafx.scene.control包中。控件类是Control类的一个子类,直接或间接,而后者又继承自Region。回想一下,Region类继承自Parent类。所以,技术上来说,一个Control也是一个Parent。我们在前面章节中关于ParentRegion类的所有讨论也适用于所有控制相关的类。

一个Parent可以生孩子。通常,控件由另一个节点(有时是多个节点)组成,该节点是它的子节点。控件类不通过getChildren()方法公开其子类的列表,因此,您不能向它们添加任何子类。

控件类通过返回一个ObservableList<Node>getChildrenUnmodifiable()方法公开其内部不可修改的子控件列表。使用控件不需要知道控件的内部子级。然而,如果你需要他们的孩子的列表,getChildrenUnmodifiable()方法会给你。

图 12-1 显示了一些常用控件的类的类图。控件类的列表比类图中显示的要大得多。

img/336502_2_En_12_Fig1_HTML.jpg

图 12-1

JavaFX 中控件类的类图

Control类是所有控件的基类。它声明了三个属性,如表 12-1 所示,这些属性对所有控件都是通用的。

表 12-1

Control类中声明的属性

|

财产

|

类型

|

描述

| | --- | --- | --- | | contextMenu | ObjectProperty<ContextMenu> | 指定控件的内容菜单。 | | skin | ObjectProperty<Skin<?>> | 指定控件的外观。 | | tooltip | ObjectProperty<Tooltip> | 指定控件的工具提示。 |

属性指定控件的上下文菜单。上下文菜单为用户提供了一个选项列表。每个选择都是在控件的当前状态下可以对其采取的操作。有些控件有默认的上下文菜单。例如,当右键单击一个TextField时,会显示一个上下文菜单,其中包含撤销、剪切、复制和粘贴等选项。通常,当控件具有焦点时,当用户按下组合键(例如,Windows 上的 Shift + F10)或单击鼠标(Windows 上的右击)时,会显示上下文菜单。在讨论文本输入控件时,我将再次讨论contextMenu属性。

在撰写本文时,JavaFX 不允许访问或定制控件的默认上下文菜单。即使控件有默认的上下文菜单,contextMenu属性也是null。当您设置contextMenu属性时,它将替换控件的默认上下文。请注意,并非所有控件都有默认的上下文菜单,并且上下文菜单并不适合所有控件。例如,Button控件不使用上下文菜单。

控件的视觉外观被称为它的皮肤。外观通过更改其视觉外观来响应控件中的状态更改。一个皮肤由一个Skin接口的实例来表示。Control类实现了Skinnable接口,给予所有控件使用皮肤的能力。

Control类中的skin属性指定控件的自定义皮肤。开发新皮肤不是一件容易的事情。在大多数情况下,您可以使用 CSS 样式自定义控件的外观。所有的控件都可以使用 CSS 来设置样式。Control类实现了Styleable接口,所以所有的控件都可以被样式化。关于如何使用 CSS 的更多细节,请参考第八章。我将在本章中讨论一些常用的 CSS 属性。

当鼠标在控件上停留一小段时间时,控件会显示一条名为工具提示的短消息。Tooltip类的对象表示 JavaFX 中的工具提示。Control类中的tooltip属性指定控件的工具提示。

标签控件

一个labeled控件包含一个只读的文本内容和一个可选的图形作为其 UI 的一部分。LabelButtonCheckBoxRadioButtonHyperlink是 JavaFX 中标签控件的一些例子。所有带标签的控件都直接或间接地继承自被声明为抽象的Labeled类。Labeled类继承自Control类。图 12-2 显示了标签控件的类图。为了简洁起见,图中省略了一些类。

img/336502_2_En_12_Fig2_HTML.jpg

图 12-2

标记控件类的类图

Labeled类声明了textgraphic属性,分别代表文本和图形内容。它声明了几个其他属性来处理其内容的视觉方面,例如,对齐、字体、填充和文本换行。表 12-2 包含这些属性的列表及其简要描述。我将在随后的章节中讨论其中的一些属性。

表 12-2

Labeled类中声明的属性

|

财产

|

类型

|

描述

| | --- | --- | --- | | alignment | ObjectProperty<Pos> | 它指定内容区域内控件内容的对齐方式。当内容区域大于内容(文本+图形)时,其效果是可见的。默认值为Pos.CENTER_LEFT。 | | contentDisplay | ObjectProperty<ContentDisplay> | 它指定图形相对于文本的位置。 | | ellipsisString | StringProperty | 它指定当文本被截断时为省略号显示的字符串,因为控件的大小小于首选大小。对于大多数语言环境,默认值是"..."。为此属性指定空字符串不会在截断的文本中显示省略号字符串。 | | font | ObjectProperty<Font> | 它指定文本的默认字体。 | | graphic | ObjectProperty<Node> | 它为控件指定一个可选图标。 | | graphicTextGap | DoubleProperty | 它指定了图形和文本之间的文本数量。 | | labelPadding | ReadOnlyObjectProperty<Insets> | 它是控件内容区域周围的空白。默认为Insets.EMPTY。 | | lineSpacing | DoubleProperty | 它指定当控件显示多行时相邻行之间的间距。 | | mnemonicParsing | BooleanProperty | 它启用或禁用文本分析来检测助记符。如果设置为 true,则分析控件文本中的下划线(_)字符。第一个下划线后面的字符作为控件的助记键添加。在 Windows 计算机上按 Alt 键会突出显示所有控件的助记键。 | | textAlignment | ObjectProperty<TextAlignment> | 它为多行文字指定文字边界内的文字对齐方式。 | | textFill | ObjectProperty<Paint> | 它指定文本颜色。 | | textOverrun | ObjectProperty<OverrunStyle> | 它指定当文本内容超出可用空间时如何显示文本。 | | text | StringProperty | 它指定文本内容。 | | underline | BooleanProperty | 它指定文本内容是否应该加下划线。 | | wrapText | BooleanProperty | 它指定如果文本不能在一行中显示,是否应该换行。 |

定位图形和文本

标签控件的contentDisplay属性指定图形相对于文本的位置。其值是ContentDisplay枚举的常量之一:TOPRIGHTBOTTOMLEFTCENTERTEXT_ONLYGRAPHIC_ONLY。如果不想显示文本或图形,可以使用GRAPHIC_ONLYTEXT_ONLY值,而不是将文本设置为空字符串,将图形设置为null。图 12-3 显示了对一个LabelcontentDisplay属性使用不同值的效果。Label使用 Name:作为文本,蓝色矩形作为图形。contentDisplay属性的值显示在每个实例的底部。

img/336502_2_En_12_Fig3_HTML.jpg

图 12-3

contentDisplay属性对标签控件的影响

理解助记符和加速器

标签控件支持键盘助记符,也称为键盘快捷键键盘指示器。助记键是向控件发送ActionEvent的键。助记键通常与修饰键(如 Alt 键)一起按下。修改键依赖于平台;但是,它通常是一个 Alt 键。例如,假设您将 C 键设置为关闭按钮的助记键。当您按 Alt + C 时,关闭按钮被激活。

在 JavaFX 中找到关于助记符的文档并不容易。它隐藏在LabeledScene类的文档中。为标签控件设置助记键很容易。您需要在文本内容中的助记符前面加一个下划线,并确保控件的mnemonicParsing属性设置为 true。第一个下划线被删除,其后的字符被设置为控件的助记键。对于一些带标签的控件,助记符解析默认设置为 true,而对于其他控件,您需要设置它。

Tip

并非所有平台都支持助记符。至少在 Windows 上,控件文本中的助记符没有下划线,直到按下 Alt 键。

以下语句将 C 键设置为Close按钮的助记键:

// For Button, mnemonic parsing is true by default
Button closeBtn = new Button("_Close");

当您按下 Alt 键时,所有控件的助记符都带有下划线,按下任何控件的助记符都会将焦点设置到该控件并向其发送一个ActionEvent

JavaFX 在javafx.scene.input包中提供了以下四个类,以编程方式为所有类型的控件设置助记符:

  • Mnemonic

  • KeyCombination

  • KeyCharacterCombination

  • KeyCodeCombination

Mnemonic类的一个对象代表一个助记符。被声明为抽象的KeyCombination类的对象代表助记键的组合键。KeyCharacterCombinationKeyCodeCombination类是KeyCombination类的子类。使用前者用一个字符构造一个组合键;使用后者通过一个键码构造一个组合键。请注意,并非键盘上的所有键都代表字符。KeyCodeCombination类允许你为键盘上的任意键创建组合键。

为一个节点创建了Mnemonic对象,并将其添加到一个Scene中。当Scene接收到组合键的未使用的键事件时,它向目标节点发送一个ActionEvent

以下代码片段实现了与前面示例中使用一条语句相同的结果:

Button closeBtn = new Button("Close");

// Create a KeyCombination for Alt + C
KeyCombination kc = new KeyCodeCombination(KeyCode.C, KeyCombination.ALT_DOWN);

// Create a Mnemonic object for closeBtn
Mnemonic mnemonic = new Mnemonic(closeBtn, kc);

Scene scene = create a scene...;
scene.addMnemonic(mnemonic); // Add the mnemonic to the scene

KeyCharacterCombination类也可以用来创建 Alt + C 的组合键:

KeyCombination kc = new KeyCharacterCombination("C", KeyCombination.ALT_DOWN);

Scene类支持快捷键。当按下加速键时,执行一个Runnable任务。注意助记键和快捷键的区别。助记键与控件相关联,按下它的组合键会向控件发送一个ActionEvent。快捷键不与控件关联,而是与任务关联。Scene类维护一个ObservableMap<KeyCombination, Runnable>,其引用可以使用getAccelerators()方法获得。

下面的代码片段将一个快捷键(Windows 上的 Ctrl + X 和 Mac 上的 Meta + X)添加到一个Scene,它关闭与Scene关联的窗口。SHORTCUT键代表平台上的快捷键 Windows 上的 Ctrl,Mac 上的 Meta:

Scene scene = create a scene object...;
...
KeyCombination kc = new KeyCodeCombination(KeyCode.X,
                                           KeyCombination.SHORTCUT_DOWN);
Runnable task = () -> scene.getWindow().hide();
scene.getAccelerators().put(kc, task);

清单 12-1 中的程序展示了如何使用助记符和快捷键。按 Alt + 1 和 Alt + 2 分别激活按钮 1 和按钮 2。按下这些按钮会改变Label的文本。按快捷键+ X 将关闭窗口。

// MnemonicTest.java
package com.jdojo.control;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.Mnemonic;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

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

        @Override
        public void start(Stage stage) {
                VBox root = new VBox();
                root.setSpacing(10);
                root.setStyle("-fx-padding: 10;" +
                              "-fx-border-style: solid inside;" +
                              "-fx-border-width: 2;" +
                              "-fx-border-insets: 5;" +
                              "-fx-border-radius: 5;" +
                              "-fx-border-color: blue;");

                Scene scene = new Scene(root);
                Label msg = new Label(
                      "Press Ctrl + X on Windows \nand " +
                      "\nMeta + X on Mac to close the window");
                Label lbl = new Label("Press Alt + 1 or Alt + 2");

                // Use Alt + 1 as the mnemonic for Button 1
                Button btn1 = new Button("Button _1");
                btn1.setOnAction(e -> lbl.setText("Button 1 clicked!"));

                // Use Alt + 2 as the mnemonic key for Button 2
                Button btn2 = new Button("Button 2");
                btn2.setOnAction(e ->
                         lbl.setText("Button 2 clicked!"));
                KeyCombination kc =
                         new KeyCodeCombination(KeyCode.DIGIT2,
                           KeyCombination.ALT_DOWN);
                Mnemonic mnemonic = new Mnemonic(btn2, kc);
                scene.addMnemonic(mnemonic);

                // Add an accelarator key to the scene
                KeyCombination kc4 =
                    new KeyCodeCombination(KeyCode.X,
                                 KeyCombination.SHORTCUT_DOWN);
                Runnable task = () -> scene.getWindow().hide();
                scene.getAccelerators().put(kc4, task);

                // Add all children to the VBox
                root.getChildren().addAll(msg, lbl, btn1, btn2);

                stage.setScene(scene);
                stage.setTitle("Using Mnemonics and Accelerators");
                stage.show();
        }
}

Listing 12-1Using Mnemonics and Accelerator Keys

了解标签控件

Label类的一个实例代表一个标签控件。顾名思义,Label只是一个标签,用来识别或描述屏幕上的另一个组件。它可以显示文本和/或图标。通常,Label被放置在它所描述的节点的旁边(右边或左边)或顶部。

A Label是不可穿越的焦点。也就是说,您不能使用 Tab 键将焦点设置为Label。一个Label控件不会产生任何在应用程序中通常使用的有趣事件。

一个Label控件也可以用来显示文本,如果没有足够的空间来显示整个文本,可以截断文本。请参考关于Labeled类的textOverrunellipsisString属性的 API 文档,了解更多关于如何在Label控件中控制文本截断行为的细节。

图 12-4 显示了一个带有两个Label控件的窗口,控件上有文字名字:和姓氏:。带有文本 First Name:的Label是一个指示器,指示用户应该在紧挨着它的字段中输入名字。类似的争论也适用于最后一个名字:Label控制。

img/336502_2_En_12_Fig4_HTML.jpg

图 12-4

带有两个Label控件的窗口

Label类有一个非常有用的ObjectProperty<Node>类型的labelFor属性。它被设置为场景图中的另一个节点。一个Label控件可以有助记符。默认情况下,Label控件的助记符解析设置为 false。当您按下Label的助记键时,焦点被设置到该LabellabelFor节点。下面的代码片段创建了一个TextField和一个LabelLabel设置助记符,启用助记符解析,并将TextField设置为其labelFor属性。当 Alt + F 键被按下时,焦点移动到TextField:

TextField fNameFld = new TextField();
Label fNameLbl = new Label("_First Name:"); // F is mnemonic
fNameLbl.setLabelFor(fNameFld);
fNameLbl.setMnemonicParsing(true);

清单 12-2 中的程序产生如图 12-4 所示的屏幕。按 Alt + F 和 Alt + L 在两个TextField控件之间切换焦点。

// LabelTest.java
// ... find in the book's download area.

Listing 12-2Using the Label Control

了解按钮

JavaFX 提供了三种代表按钮的控件:

  • 执行命令的按钮

  • 做出选择的按钮

  • 执行命令和做出选择的按钮

所有按钮类都继承自ButtonBase类。类图请参见图 12-2 。所有类型的按钮都支持ActionEvent。按钮被激活时会触发一个ActionEvent。可以用不同的方式激活按钮,例如,使用鼠标、助记键、加速键或其他组合键。

激活时执行命令的按钮称为命令按钮ButtonHyperlinkMenuButton类代表命令按钮。MenuButton让用户执行命令列表中的一个命令。用于向用户呈现不同选择的按钮被称为选择按钮ToggleButtonCheckBoxRadioButton类代表选择按钮。第三种按钮是前两种的混合。它们让用户执行命令或做出选择。SplitMenuButton类代表一个混合按钮。

Tip

所有按钮都标记为控件。因此,它们可以有文本内容和/或图形。所有类型的按钮都能够触发ActionEvent

了解命令按钮

您已经在多个实例中使用了命令按钮,例如,关闭窗口的关闭按钮。在这一节中,我将讨论用作命令按钮的按钮。

了解按钮控件

Button类的一个实例代表一个命令按钮。通常情况下,Button的标签是文本,并且向它注册了一个ActionEvent处理程序。默认情况下,Button类的mnemonicParsing属性被设置为 true。

Button可以处于三种模式之一:

  • 普通按钮

  • 默认按钮

  • 取消按钮

对于一个普通的按钮,当按钮被激活时,它的ActionEvent被触发。对于默认按钮,当 Enter 键被按下并且场景中没有其他节点消耗按键时,触发ActionEvent。对于“取消”按钮,当按下 Esc 键并且场景中没有其他节点消耗该按键时,会触发ActionEvent

默认情况下,Button是一个普通按钮。默认和取消模式由defaultButtoncancelButton属性表示。您可以将这些属性之一设置为 true,使按钮成为默认按钮或取消按钮。默认情况下,这两个属性都设置为 false。

下面的代码片段创建了一个普通的Button并添加了一个ActionEvent处理程序。当按钮被激活时,例如,用鼠标点击,调用newDocument()方法:

// A normal button
Button newBtn = new Button("New");
newBtn.setOnAction(e -> newDocument());

下面的代码片段创建了一个默认按钮并添加了一个ActionEvent处理程序。当按钮被激活时,调用save()方法。请注意,如果场景中没有其他节点消耗按键,默认的Button也会通过按回车键激活:

// A default button
Button saveBtn = new Button("Save");
saveBtn.setDefaultButton(true); // Make it a default button
saveBtn.setOnAction(e -> save());

清单 12-3 中的程序创建了一个正常按钮、一个默认按钮和一个取消按钮。它向所有三个按钮添加了一个ActionEvent监听器。请注意,所有按钮都有助记符(例如,N 代表New按钮)。当按钮被激活时,一条信息显示在Label中。您可以通过不同方式激活按钮:

  • 点击按钮

  • 使用 Tab 键和空格键将焦点设置到按钮上

  • 按 Alt 键和它们的助记键

  • 按下输入键激活Save按钮

  • 按 Esc 键激活Cancel按钮

无论您如何激活按钮,都会调用它们的ActionEvent处理程序。通常,按钮的ActionEvent处理程序包含按钮的命令。

// ButtonTest.java
// ... find in the book's download area.

Listing 12-3Using the Button Class to Create Command Buttons

Tip

可以将场景中的多个按钮设置为默认按钮或取消按钮。但是,只使用第一个。在一个场景中声明多个默认按钮和取消按钮是糟糕的设计。默认情况下,JavaFX 用浅色突出显示默认按钮,使其具有独特的外观。您可以使用 CSS 样式自定义默认按钮和取消按钮的外观。将同一个按钮设置为默认按钮和取消按钮也是允许的,但是这样做是糟糕设计的标志。

一个Button的默认 CSS 样式类名是buttonButton类支持两个 CSS 伪类:defaultcancel。您可以使用这些伪类来自定义“寻找默认值”和“取消”按钮。以下 CSS 样式将默认按钮的文本颜色设置为蓝色,取消按钮的文本颜色设置为灰色:

.button:default {
        -fx-text-fill: blue;
}

.button:cancel {
        -fx-text-fill: gray;
}

Tip

您可以使用 CSS 样式来创建时尚的按钮。请访问网站 http://fxexperience.com/2011/12/styling-fx-buttons-with-css/ 查看示例。

了解超链接控件

Hyperlink类的一个实例表示一个超链接控件,看起来像网页中的超链接。在网页中,超链接用于导航到另一个网页。然而,在 JavaFX 中,当一个Hyperlink控件被激活时,例如通过点击它,就会触发一个ActionEvent,并且您可以在ActionEvent处理程序中自由地执行任何操作。

一个Hyperlink控件只是一个看起来像超链接的按钮。默认情况下,助记符解析是关闭的。一个Hyperlink控件可以有焦点,默认情况下,当它有焦点时,它会绘制一个虚线矩形边框。当鼠标光标悬停在一个Hyperlink控件上时,光标会变成一个手形,并且其文本带有下划线。

Hyperlink类包含一个BooleanProperty类型的visited属性。当Hyperlink控件第一次被激活时,它被认为是“被访问过的”,并且visited属性被自动设置为真。所有访问过的超链接以不同于未访问过的颜色显示。您也可以使用Hyperlink类的setVisited()方法手动设置visited属性。

下面的代码片段创建了一个文本为"JDojo"Hyperlink控件,并为Hyperlink添加了一个ActionEvent处理程序。当Hyperlink被激活时, www.jdojo.com 网页在WebView中打开,这是另一个显示网页的 JavaFX 控件。在这里,我将使用它,不做任何解释:

Hyperlink jdojoLink = new Hyperlink("JDojo");
WebView webview = new WebView();
jdojoLink.setOnAction(e -> webview.getEngine().load("http://www.jdojo.com"));

清单 12-4 中的程序向一个BorderPane的顶部区域添加了三个Hyperlink控件。一个WebView控件被添加到中心区域。当您单击其中一个超链接时,会显示相应的网页。

// HyperlinkTest.java
// ... find in the book's download area.

Listing 12-4Using the Hyperlink Control

了解菜单按钮控件

一个控件看起来像一个按钮,行为像一个菜单。当它被激活时(通过单击或其他方式),它会以弹出菜单的形式显示一个选项列表。菜单中的选项列表保存在一个ObservableList<MenuItem>中,其引用由getItems()方法返回。要在菜单选项被选中时执行命令,您需要将ActionEvent处理程序添加到MenuItem

下面的代码片段创建了一个带有两个MenuItemMenuButton,每个菜单项都有一个ActionEvent处理程序。图 12-5 显示MenuButton处于不显示和显示两种状态。

img/336502_2_En_12_Fig5_HTML.jpg

图 12-5

MenuButton处于不显示和显示状态

// Create two menu items with an ActionEvent handler.
// Assume that the loadPage() method exists
MenuItem jdojo = new MenuItem("JDojo");
jdojo.setOnAction(e -> loadPage("http://www.jdojo.com"));

MenuItem yahoo = new MenuItem("Yahoo");
yahoo.setOnAction(e -> loadPage("http://www.yahoo.com"));

// Create a MenuButton and the two menu items
MenuButton links = new MenuButton("Visit");
links.getItems().addAll(jdojo, yahoo);

MenuButton类声明了两个属性:

  • popupSide

  • showing

popupSide属性为ObjectProperty<Side>类型,showing属性为ReadOnlyBooleanProperty类型。

属性决定了菜单的哪一面应该被显示。其值是Side枚举中的常量之一:TOPLEFTBOTTOMRIGHT。默认值为Side.BOTTOMMenuItem中的箭头表示由popupSide属性设置的方向。图 12-5 中箭头向下,表示popupSide属性设置为Side.BOTTOM。只有在该侧有空间显示菜单时,菜单才会按popupSide属性中设置的方向打开。如果没有可用的空间,JavaFX 运行时将明智地决定菜单应该显示在哪一边。当弹出菜单显示时,showing属性的值为真。否则就是假的。

清单 12-5 中的程序使用MenuButton控件创建了一个应用程序,其工作方式类似于清单 12-4 中使用Hyperlink控件的程序。运行应用程序,点击窗口右上方的访问MenuButton,选择要打开的页面。

// MenuButtonTest.java
// ... find in the book's download area.

Listing 12-5Using the MenuButton Control

了解选择按钮

JavaFX 提供了几个控件,用于从可用选项列表中进行一个或多个选择:

  • ToggleButton

  • CheckBox

  • RadioButton

Tip

JavaFX 还提供了ChoiceBoxComboBoxListView控件,允许用户从多个可用选项中进行选择。我将在单独的部分讨论这些控件。

这三个控件都被标记为控件,它们帮助您以不同的格式向用户提供多种选择。可用选择的数量可以从 2 到 N 变化,其中 N 是大于 2 的数。

从可用选项中进行选择可能是互斥的。也就是说,用户只能从选项列表中做出一个选择。如果用户改变选择,则自动取消选择先前的选择。例如,MaleFemaleUnknown三个选项的性别选择列表是互斥的。用户必须只选择三个选项中的一个,而不是两个或更多。在这种情况下通常使用ToggleButtonRadioButton控制。

有一种特殊的选择情况,选择的数量是两个。在这种情况下,选择属于boolean类型:对或错。有时,它也被称为是/否开/关选择。在这种情况下通常使用ToggleButtonCheckBox控件。

有时,用户可以从选项列表中进行多项选择。例如,您可以向用户提供一个爱好列表,让用户从列表中选择零个或多个爱好。这种情况下通常使用ToggleButtonCheckBox控件。

了解切换按钮控件

ToggleButton是一个双态按钮控件。这两种状态是选中未选中。它的selected属性表示它是否被选中。当selected属性处于选中状态时为真。否则就是假的。当它处于选中状态时,它会保持按下状态。按下它可以在选中和未选中状态之间切换,因此得名ToggleButton。对于ToggleButton来说,助记符解析是默认启用的。

图 12-6 显示了四个标签为春、夏、秋、冬的切换按钮。其中两个切换按钮“弹簧”和“下落”被选中,另外两个未选中。

img/336502_2_En_12_Fig6_HTML.jpg

图 12-6

显示四个切换按钮的窗口

使用下面的代码,您可以像创建Button一样创建一个ToggleButton:

ToggleButton springBtn = new ToggleButton("Spring");

一个ToggleButton用于选择一个选项,而不是执行一个命令。通常情况下,您不会将ActionEvent处理程序添加到ToggleButton中。有时,您可以使用ToggleButton来开始或停止一个动作。为此,您需要为其选定的属性添加一个ChangeListener

Tip

每次单击ToggleButton时,都会调用它的ActionEvent处理程序。请注意,第一次单击选择了一个ToggleButton,第二次单击取消了选择。如果您选择和取消选择一个ToggleButton,那么ActionEvent处理程序将被调用两次。

可在一组中使用切换按钮,从中可选择零个或一个ToggleButton。要将切换按钮添加到组中,您需要将它们添加到一个ToggleGroup中。ToggleButton类包含一个 t oggleGroup属性。要将ToggleButton添加到ToggleGroup中,请将ToggleButtontoggleGroup属性设置为组。将toggleGroup属性设置为null会从组中删除一个ToggleButton。下面的代码片段创建了四个切换按钮,并将它们添加到一个ToggleGroup中:

ToggleButton springBtn = new ToggleButton("Spring");
ToggleButton summerBtn = new ToggleButton("Summer");
ToggleButton fallBtn = new ToggleButton("Fall");
ToggleButton winterBtn = new ToggleButton("Winter");

// Create a ToggleGroup
ToggleGroup group = new ToggleGroup();

// Add all ToggleButtons to the ToggleGroup
springBtn.setToggleGroup(group);
summerBtn.setToggleGroup(group);
fallBtn.setToggleGroup(group);
winterBtn.setToggleGroup(group);

每个ToggleGroup保持一个ObservableList<Toggle>。注意,Toggle是一个由ToggleButton类实现的接口。ToggleGroup类的getToggles()方法返回组中Toggle的列表。通过将ToggleButton添加到由getToggles()方法返回的列表中,可以将ToggleButton添加到组中。前面的代码片段可以重写如下:

ToggleButton springBtn = new ToggleButton("Spring");
ToggleButton summerBtn = new ToggleButton("Summer");
ToggleButton fallBtn = new ToggleButton("Fall");
ToggleButton winterBtn = new ToggleButton("Winter");

// Create a ToggleGroup
ToggleGroup group = new ToggleGroup();

// Add all ToggleButtons to the ToggleGroup
group.getToggles().addAll(springBtn, summerBtn, fallBtn, winterBtn);

ToggleGroup类包含一个selectedToggle属性,用于跟踪组中选定的TogglegetSelectedToggle()方法返回被选中的Toggle的引用。如果组中没有选择Toggle,则返回null。如果您想跟踪在一个ToggleGroup中选择的变化,那么就给这个属性添加一个ChangeListener

Tip

您可以在一个ToggleGroup中选择零个或一个ToggleButton。选择群组中的ToggleButton会取消选择已经选择的ToggleButton。点击一个组中已经选中的ToggleButton会取消选中它,使该组中没有ToggleButton被选中。

清单 12-6 中的程序为一个ToggleGroup添加了四个切换按钮。您可以从组中选择无或最多一个ToggleButton。图 12-7 显示了两个截图:一个是没有选择的时候,一个是选择了标签为 Summer 的ToggleButton的时候。程序向组中添加一个ChangeListener来跟踪选择的变化,并在一个Label控件中显示所选ToggleButton的标签。

img/336502_2_En_12_Fig7_HTML.jpg

图 12-7

一个ToggleGroup中的四个切换按钮允许一次选择一个按钮

// ToggleButtonTest.java
// ... find in the book's download area.

Listing 12-6Using Toggle Buttons in a ToggleGroup and Tracking the Selection

了解单选按钮控件

RadioButton类的一个实例代表一个单选按钮。它继承自ToggleButton类。因此,它具有切换按钮的所有功能。与切换按钮相比,单选按钮的呈现方式不同。像切换按钮一样,单选按钮可以处于两种状态之一:选中未选中。它的selected属性表示它的当前状态。像切换按钮一样,它的助记符解析默认是启用的。就像一个切换按钮,当它被选中和取消选中时,它也会发送一个ActionEvent。图 12-8 显示了一个文本为 Summer 的RadioButton处于选中和未选中状态。

img/336502_2_En_12_Fig8_HTML.png

图 12-8

显示处于选中和未选中状态的单选按钮

单选按钮的使用与切换按钮的使用有很大的不同。回想一下,当在一个组中使用切换按钮时,该组中可能没有任何选定的切换按钮。当在组中使用单选按钮时,组中必须有一个选中的单选按钮。与切换按钮不同,单击组中选定的单选按钮不会取消对它的选择。为了强制执行必须在一组单选按钮中选择一个单选按钮的规则,默认情况下以编程方式从该组中选择一个单选按钮。

Tip

当用户必须从选项列表中进行选择时,可以使用单选按钮。当用户可以从选项列表中进行选择或不选择时,使用切换按钮。

清单 12-7 中的程序展示了如何在ToggleGroup中使用单选按钮。图 12-9 显示了运行代码结果的窗口。该程序与之前使用切换按钮的程序非常相似。使用以下代码,Summer 被设置为默认选择:

// Select the default season as Summer
summerBtn.setSelected(true);

将更改监听器添加到组中后,在单选按钮中设置默认季节,以便正确更新显示所选季节的消息。

img/336502_2_En_12_Fig9_HTML.jpg

图 12-9

一个ToggleGroup中的四个单选按钮

// RadioButtonTest.java
// ... find in the book's download area.

Listing 12-7Using Radio Buttons in a ToggleGroup and Tracking the Selection

了解复选框控件

CheckBox是三态选择控件:选中未选中未定义。未定义状态也称为不确定状态。A CheckBox支持三种选择:真/假/未知或是/否/未知。通常,CheckBox有文本作为标签,但没有图形(尽管它可以)。点击CheckBox将其从一种状态转换到另一种状态,在三种状态之间循环。

为一个CheckBox画一个方框。在未选中状态下,该框为空。当复选框处于选中状态时,它会显示一个勾号(或复选标记)。在未定义状态下,框中会出现一条水平线。图 12-10 显示了标记为饥饿的CheckBox的三种状态。

img/336502_2_En_12_Fig10_HTML.png

图 12-10

显示处于未选中、选中和未定义状态的复选框

默认情况下,CheckBox控件只支持两种状态:选中未选中allowIndeterminate属性指定第三种状态(未定义状态)是否可供选择。默认情况下,它设置为 false:

// Create a CheckBox that supports checked and unchecked states only
CheckBox hungryCbx = new CheckBox("Hungry");

// Create a CheckBox and configure it to support three states
CheckBox agreeCbx = new CheckBox("Hungry");
agreeCbx.setAllowIndeterminate(true);

CheckBox类包含selectedindeterminate属性来跟踪它的三种状态。如果indeterminate属性为真,则处于未定义状态。如果indeterminate属性为 false,则它是已定义的,并且可能处于选中或未选中状态。如果indeterminate属性为假而selected属性为真,则处于选中状态。如果indeterminate属性为假,selected属性为假,则处于未选中状态。表 12-3 总结了确定复选框状态的规则。

表 12-3

根据复选框的不确定属性和选定属性确定其状态

|

不确定

|

选中

|

状态

| | --- | --- | --- | | false | true | 检查 | | false | false | 未加抑制的 | | true | true/false | 不明确的 |

有时,您可能想要检测复选框中的状态转换。因为复选框在两个属性中维护状态信息,所以您需要向这两个属性添加一个ChangeListener。当一个复选框被点击时,一个ActionEvent被触发。你也可以使用一个ActionEvent来检测复选框的状态变化。下面的代码片段展示了如何使用两个ChangeListener来检测一个CheckBox中的状态变化。假设changed()方法和代码的其余部分属于同一个类:

// Create a CheckBox to support three states
CheckBox agreeCbx = new CheckBox("I agree");
agreeCbx.setAllowIndeterminate(true);

// Add a ChangeListener to the selected and indeterminate properties
agreeCbx.selectedProperty().addListener(this::changed);
agreeCbx.indeterminateProperty().addListener(this::changed);
...
// A change listener to track the selection in the group
public void changed(ObservableValue<? extends Boolean> observable,
                    Boolean oldValue,
                    Boolean newValue) {
        String state = null;
        if (agreeCbx.isIndeterminate()) {
                state = "Undefined";
        } else if (agreeCbx.isSelected()) {
                state = "Checked";
        } else {
                state = "Unchecked";
        }
        System.out.println(state);
}

清单 12-8 中的程序展示了如何使用CheckBox控件。图 12-11 显示了运行该代码产生的窗口。程序创建了两个CheckBox控件。饥饿的 ?? 只支持两个州。我同意CheckBox配置为支持三种状态。当您通过单击“我同意”CheckBox来更改其状态时,顶部的Label会显示该状态的描述。

img/336502_2_En_12_Fig11_HTML.jpg

图 12-11

两个复选框:一个使用两种状态,一个使用三种状态

// CheckBoxTest.java
// ... find in the book's download area.

Listing 12-8Using the CheckBox Control

一个CheckBox的默认 CSS 样式类名是check-boxCheckBox类支持三个 CSS 伪类:selecteddeterminateindeterminate。当selected属性为真时,selected伪类适用。当indeterminate属性为假时,determinate伪类适用。当indeterminate属性为真时,indeterminate伪类适用。

CheckBox控件包含两个子结构:boxmark。您可以设计它们的样式来改变它们的外观。您可以更改框的背景色和边框,也可以更改刻度线的颜色和形状。box 和 mark 都是StackPane的实例。显示的刻度线给出了StackPane的形状。您可以通过在 CSS 中提供不同的形状来更改标记的形状。通过更改标记的背景颜色,可以更改刻度线的颜色。下面的 CSS 将用褐色显示盒子,用红色显示刻度线:

.check-box .box {
        -fx-background-color: tan;
}

.check-box:selected .mark {
    -fx-background-color: red;
}

了解混合动力按钮控制

根据我们对不同按钮类型的定义,一个SplitMenuButton属于混合型。它结合了弹出式菜单和命令按钮的功能。它让你像选择一个MenuButton控件一样选择一个动作,像执行一个Button控件一样执行一个命令。SplitMenuButton类继承自MenuButton类。

一个SplitMenuButton分为两个区域:动作区和菜单打开区。当您在操作区域中单击时,ActionEvent被触发。注册的ActionEvent处理程序执行命令。单击菜单打开区域时,会显示一个菜单,用户可以从中选择要执行的操作。Mnemonic默认启用SplitMenuButton解析。

图 12-12 显示了一个SplitMenuButton处于两种状态。左边的图片显示它处于折叠状态。在右图中,它显示了菜单项。请注意将控件分成两半的垂直线。包含文本 Home 的那一半是操作区域。包含向下箭头的另一半是菜单打开区域。

img/336502_2_En_12_Fig12_HTML.png

图 12-12

处于折叠和显示状态的

您可以使用以下代码的构造器创建一个有菜单项或没有菜单项的SplitMenuButton:

// Create an empty SplitMenuItem
SplitMenuButton splitBtn = new SplitMenuButton();
splitBtn.setText("Home"); // Set the text as "Home"

// Create MenuItems
MenuItem jdojo = new MenuItem("JDojo");
MenuItem yahoo = new MenuItem("Yahoo");
MenuItem google = new MenuItem("Google");

// Add menu items to the MenuButton
splitBtn.getItems().addAll(jdojo, yahoo, google);

您需要添加一个ActionEvent处理程序,以便在动作区域中单击SplitMenuButton时执行一个动作:

// Add ActionEvent handler when "Home" is clicked
splitBtn.setOnAction(e -> /* Take some action here */);

清单 12-9 中的程序展示了如何使用SplitMenuButton。它在一个BorderPane的右上角区域添加了一个带有文本 Home 和三个菜单项的SplitMenuButton。在中心区域增加一个WebView。当你点击主页时,打开 www.jdojo.com 网页。当您通过单击向下箭头使用菜单选择网站时,相应的网站将会打开。该程序与您之前使用MenuButtonHyperlink控件开发的程序非常相似。

// SplitMenuButtonTest.java
// ... find in the book's download area.

Listing 12-9Using the SplitMenuButton Control

从项目列表中进行选择

在前面几节中,您已经看到了如何向用户显示一个项目列表,例如,使用切换按钮和单选按钮。切换和单选按钮更容易使用,因为所有选项对用户总是可见的。然而,它们占用了大量的屏幕空间。考虑使用单选按钮向用户显示美国所有 50 个州的名称。这会占用很多空间。有时,列表中的所有可用项目都不适合选择,因此您需要给用户一个机会来输入列表中没有的新项目。

JavaFX 提供了一些允许用户从项目列表中选择项目的控件。与按钮相比,它们占用更少的空间。它们提供高级功能来自定义它们的外观和行为。我将在后续章节中讨论以下此类控件:

  • ChoiceBox

  • ComboBox

  • ListView

  • ColorPicker

  • DatePicker

ChoiceBox允许用户从预定义项目的小列表中选择一个项目。ComboBoxChoiceBox的高级版本。它有很多特性,例如,可以编辑或者改变列表中项目的外观,这些都是ChoiceBox中没有的。ListView为用户提供从项目列表中选择多个项目的能力。通常情况下,用户始终可以看到ListView中的所有或多个项目。ColorPicker允许用户从标准调色板中选择一种颜色,或以图形方式定义自定义颜色。DatePicker允许用户从日历弹出窗口中选择日期。用户可以选择以文本形式输入日期。ComboBoxColorPickerDatePicker具有相同的超类ComboBoxBase

了解选择框控件

ChoiceBox用于让用户从一个小项目列表中选择一个项目。这些项目可以是任何类型的对象。ChoiceBox是一个参数化类。参数类型是列表中项目的类型。如果您想在一个ChoiceBox中存储混合类型的项目,您可以使用它的类型,如下面的代码所示:

// Create a ChoiceBox for any type of items
ChoiceBox<Object> seasons = new ChoiceBox<>();

// Instead create a ChoiceBox for String items
ChoiceBox<String> seasons = new ChoiceBox<>();

您可以在使用以下代码创建ChoiceBox时指定列表项:

ObservableList<String> seasonList = FXCollections.<String>observableArrayList(
        "Spring", "Summer", "Fall", "Winter");
ChoiceBox<String> seasons = new ChoiceBox<>(seasonList);

在您创建了一个ChoiceBox之后,您可以使用items属性将项目添加到它的项目列表中,该属性属于ObjectProperty<ObservableList<T>>类型,其中TChoiceBox的类型参数。以下代码将完成这一任务:

ChoiceBox<String> seasons = new ChoiceBox<>();
seasons.getItems().addAll("Spring", "Summer", "Fall", "Winter");

图 12-13 显示了四种不同状态下的选择框。在物品清单中有四个季节的名字。第一张图片(标记为#1)显示了没有选择时的初始状态。用户可以使用鼠标或键盘打开项目列表。单击控件内的任何位置都会在弹出窗口中打开项目列表,如标记为#2 的图片所示。当控件具有焦点时,按下向下箭头键也会打开项目列表。您可以通过单击或使用上/下箭头和 Enter 键从列表中选择一个项目。当您选择一个项目时,显示项目列表的弹出窗口被折叠,所选项目显示在控件中,如标记为#3 的图片所示。标签为#4 的图片显示了当选择一个项目(在本例中为 Spring)并显示列表项目时的控件。弹出窗口显示一个复选标记,表示控件中的该项已被选中。表 12-4 列出了在ChoiceBox类中声明的属性。

表 12-4

ChoiceBox类中声明的属性

|

财产

|

类型

|

描述

| | --- | --- | --- | | converter | ObjectProperty <StringConverter<T>> | 它充当一个转换器对象,调用该对象的toString()方法来获取列表中项目的字符串表示。 | | items | ObjectProperty <ObservableList<T>> | 这是要在ChoiceBox中显示的选项列表。 | | selectionModel | ObjectProperty <SingleSelectionModel<T>> | 它作为一个选择模型来跟踪ChoiceBox中的选择。 | | showing | ReadOnlyBooleanProperty | 它的 true 值指示控件正在向用户显示选项列表。它的 false 值表示选项列表是折叠的。 | | value | ObjectProperty<T> | 这是在ChoiceBox中选择的项目。 |

img/336502_2_En_12_Fig13_HTML.jpg

图 12-13

不同状态下的选择框

Tip

您并不局限于使用鼠标或键盘来显示项目列表。您可以分别使用show()hide()方法以编程方式显示和隐藏列表。

ChoiceBoxvalue属性存储控件中选中的项目。其类型为ObjectProperty<T>,其中T为控件的类型参数。如果用户没有选择项目,其值为null。下面的代码片段设置了value属性:

// Create a ChoiceBox for String items
ChoiceBox<String> seasons = new ChoiceBox<String>();
seasons.getItems().addAll("Spring", "Summer", "Fall", "Winter");

// Get the selected value
String selectedValue = seasons.getValue();

// Set a new value
seasons.setValue("Fall");

当您使用setValue()方法设置一个新值时,如果该值存在于项目列表中,ChoiceBox将选择控件中的指定值。可以设置项目列表中不存在的值。在这种情况下,value 属性包含新设置的项,但控件不显示它。控制项会持续显示先前选取的项目(如果有的话)。当新项目后来被添加到项目列表中时,控件显示在value属性中设置的项目。

ChoiceBox需要跟踪选中的项目及其在项目列表中的索引。为此,它使用一个单独的对象,称为选择模型ChoiceBox类包含一个selectionModel属性来存储项目选择细节。ChoiceBox使用SingleSelectionModel类的一个对象作为它的选择模型,但是你可以使用你自己的选择模型。默认选择模型在几乎所有情况下都有效。选择模型为您提供了与选择相关的功能:

  • 它允许您使用列表中项目的索引来选择项目。

  • 它允许您选择列表中的第一个、下一个、上一个或最后一个项目。

  • 它允许您清除选择。

  • 它的selectedIndexselectedItem属性跟踪所选项的索引和值。您可以向这些属性添加一个ChangeListener,以处理ChoiceBox中选择的变化。当没有选择项目时,选择的指标为–1,选择的项目为null

下面的代码片段通过默认选择列表中的第一项来强制在ChoiceBox中输入一个值:

ChoiceBox<String> seasons = new ChoiceBox<>();
seasons.getItems().addAll("Spring", "Summer", "Fall", "Winter", "Fall");

// Select the first item in the list
seasons.getSelectionModel().selectFirst();

使用选择模型的selectNext()方法从列表中选择下一个项目。当最后一项已经被选中时调用selectNext()方法没有任何效果。使用selectPrevious()selectLast()方法分别选择列表中的前一项和最后一项。select(int index)select(T item)方法分别使用项目的索引和值来选择项目。注意,您也可以使用ChoiceBoxsetValue()方法,通过值从列表中选择一个项目。选择模型的clearSelection()方法清除当前选择,将ChoiceBox返回到好像没有选择任何项目的状态。

清单 12-10 中的程序显示如图 12-14 所示的窗口。它使用一个带有四季列表的ChoiceBox。默认情况下,程序从列表中选择第一个季节。默认情况下,应用程序会强制用户选择一个季节名称。它将ChangeListener添加到选择模型的selectedIndexselectedItem属性中。他们在标准输出上打印选择更改的详细信息。当前选择显示在一个Label控件中,该控件的text属性绑定到ChoiceBoxvalue属性。从列表中选择不同的项目,并观察标准输出和窗口以了解详细信息。

img/336502_2_En_12_Fig14_HTML.jpg

图 12-14

带有预选项目的选择框

// ChoiceBoxTest.java
// ... find in the book's download area.

Listing 12-10Using ChoiceBox with a Preselected Item

选择框中使用域对象

在前面的例子中,您使用了String对象作为选择框中的项目。您可以使用任何对象类型作为项目。ChoiceBox调用每一项的toString()方法,并在弹出列表中显示返回值。下面的代码片段创建了一个选择框,并添加了四个Person对象作为它的项目。图 12-15 显示选择框处于showing状态。注意,这些项目是使用从Person类的toString()方法返回的String对象显示的。

img/336502_2_En_12_Fig15_HTML.jpg

图 12-15

显示四个Person对象作为其项目列表的选择框

import com.jdojo.mvc.model.Person;
import javafx.scene.control.ChoiceBox;
...
ChoiceBox<Person> persons = new ChoiceBox<>();
persons.getItems().addAll(new Person("John", "Jacobs", null),
                          new Person("Donna", "Duncan", null),
                          new Person("Layne", "Estes", null),
                          new Person("Mason", "Boyd", null));

通常,对象的toString()方法返回一个代表对象状态的String。它并不意味着提供要在选择框中显示的对象的自定义字符串表示。ChoiceBox类包含一个converter属性。这是一辆StringConverter<T>型的ObjectProperty。一个StringConverter<T>对象充当从对象类型T到字符串的转换器,反之亦然。该类被声明为抽象类,如下面的代码片段所示:

public abstract class StringConverter<T> {
        public abstract String toString(T object);
        public abstract T fromString(String string);
}

toString(T object)方法将类型T的对象转换成一个字符串。fromString(String string)方法将一个字符串转换成一个T对象。

默认情况下,选择框中的converter属性为null。如果设置了,则调用转换器的toString(T object)方法来获取项目列表,而不是项目的类的toString()方法。清单 12-11 中显示的PersonStringConverter类可以充当选择框中的转换器。请注意,您将fromString()方法中的参数string视为一个人的名字,并试图从中构造一个Person对象。您不需要为选择框实现fromString()方法。它将被用在一个ComboBox中,我接下来会讨论这个。ChoiceBox将只使用toString(Person p)方法。

// PersonStringConverter.java
package com.jdojo.control;

import com.jdojo.mvc.model.Person;
import javafx.util.StringConverter;

public class PersonStringConverter extends StringConverter<Person> {
        @Override
        public String toString(Person p) {
                return p == null?
                         null : p.getLastName() + ", " + p.getFirstName();
        }

        @Override
        public Person fromString(String string) {
                Person p = null;
                if (string == null) {
                        return p;
                }

                int commaIndex = string.indexOf(",");
                if (commaIndex == -1) {
                        // Treat the string as first name
                        p = new Person(string, null, null);
                } else {
                        // Ignoring string bounds check for brevity
                        String firstName =
                                    string.substring(commaIndex + 2);
                        String lastName = string.substring(
                                    0, commaIndex);
                        p = new Person(firstName, lastName, null);
                }
                return p;
        }
}

Listing 12-11A Person to String Converter

下面的代码片段使用了一个ChoiceBox中的转换器将项目列表中的Person对象转换成字符串。图 12-16 显示选择框处于showing状态。

img/336502_2_En_12_Fig16_HTML.jpg

图 12-16

Person在选择框中使用转换器的对象

import com.jdojo.mvc.model.Person;
import javafx.scene.control.ChoiceBox;
...
ChoiceBox<Person> persons = new ChoiceBox<>();

// Set a converter to convert a Person object to a String object
persons.setConverter(new PersonStringConverter());

// Add five person objects to the ChoiceBox
persons.getItems().addAll(new Person("John", "Jacobs", null),
                          new Person("Donna", "Duncan", null),
                          new Person("Layne", "Estes", null),
                          new Person("Mason", "Boyd", null));

选择框中允许空值

有时,选择框可能允许用户选择null作为有效选项。这可以通过使用null作为选择列表中的一项来实现,如下面的代码所示:

ChoiceBox<String> seasons = new ChoiceBox<>();
seasons.getItems().addAll(null, "Spring", "Summer", "Fall", "Winter");

前面的代码片段产生了一个如图 12-17 所示的选择框。请注意,null项显示为空白。

img/336502_2_En_12_Fig17_HTML.png

图 12-17

选择框中的选项为空

通常需要将null选项显示为自定义字符串,例如"[None]"。这可以通过转换器来实现。在上一节中,您使用了一个转换器来定制Person对象的选择。这里,您将使用转换器为null定制选择项。您也可以在一个转换器中完成这两项工作。下面的代码片段使用带有ChoiceBox的转换器将null选项转换为"[None]"。图 12-18 显示了产生的选择框。

img/336502_2_En_12_Fig18_HTML.png

图 12-18

转换为"[None]"的选择框中的null选项

ChoiceBox<String> seasons = new ChoiceBox<>();
seasons.getItems().addAll(null, "Spring", "Summer", "Fall", "Winter");

// Use a converter to convert null to "[None]"
seasons.setConverter(new StringConverter<String>() {
        @Override
        public String toString(String string) {
                return (string == null) ? "[None]" : string;
        }

        @Override
        public String fromString(String string) {
                return string;
        }
});

选择框中使用分隔符

有时,您可能希望将选择分成不同的组。假设您想在早餐菜单中显示水果和熟食,并且想将它们分开。您可以使用Separator类的一个实例来实现这一点。它在选项列表中显示为一条水平线。A Separator不可选择。下面的代码片段创建了一个选择框,其中的一项作为Separator。图 12-19 显示选择框处于展示状态。

img/336502_2_En_12_Fig19_HTML.jpg

图 12-19

使用分隔符的选择框

ChoiceBox breakfasts = new ChoiceBox();
breakfasts.getItems().addAll("Apple", "Banana", "Strawberry",
                      new Separator(),
                      "Apple Pie", "Donut", "Hash Brown");

用 CSS 对选择框进行样式化

一个ChoiceBox的默认 CSS 样式类名是choice-boxChoiceBox类支持一个showing CSS 伪类,当showing属性为真时应用。

ChoiceBox控件包含两个子结构:open-buttonarrow。您可以设计它们的样式来改变它们的外观。两者都是StackPane的实例。ChoiceBox显示在Label中选择的项目。选择列表显示在 ID 设置为choice-box-popup-menuContextMenu中。每个选项都显示在一个 id 设置为choice-box-menu-item的菜单项中。以下样式自定义ChoiceBox控件。目前,没有办法自定义单个选择框的弹出菜单。该样式将影响ChoiceBox控件在其设置级别(场景或布局窗格)的所有实例。

/* Set the text color and font size for the selected item in the control */
.choice-box .label {
        -fx-text-fill: blue;
        -fx-font-size: 8pt;
}

/* Set the text color and text font size for choices in the popup list */
#choice-box-menu-item * {
        -fx-text-fill: blue;
        -fx-font-size: 8pt;
}

/* Set background color of the arrow */
.choice-box .arrow {
        -fx-background-color: blue;
}

/* Set the background color for the open-button area */
.choice-box .open-button {
    -fx-background-color: yellow;
}

/* Change the background color of the popup */
#choice-box-popup-menu {
        -fx-background-color: yellow;
}

了解组合框控件

ComboBox用于让用户从项目列表中选择一个项目。你可以把ComboBox看作是ChoiceBox的高级版本。ComboBox高度可定制。ComboBox类继承自ComboBoxBase类,后者为所有类似ComboBox的控件提供通用功能,如ComboBoxColorPickerDatePicker。如果您想创建一个自定义控件,允许用户从弹出列表中选择一个项目,您需要从ComboBoxBase类继承您的控件。

ComboBox中的项目列表可以包括任何类型的对象。ComboBox是一个参数化类。参数类型是列表中项目的类型。如果您想在一个ComboBox中存储混合类型的项目,您可以使用它的类型,如下面的代码:

// Create a ComboBox for any type of items
ComboBox<Object> seasons = new ComboBox<>();

// Instead create a ComboBox for String items
ComboBox<String> seasons = new ComboBox<>();

您可以在创建ComboBox时指定列表项,如以下代码所示:

ObservableList<String> seasonList = FXCollections.<String>observableArrayList(
    "Spring", "Summer", "Fall", "Winter");
ComboBox<String> seasons = new ComboBox<>(seasonList);

创建组合框后,可以使用items属性将项目添加到项目列表中,该属性属于ObjectProperty<ObservableList<T>>类型,其中T是组合框的类型参数,如以下代码所示:

ComboBox<String> seasons = new ComboBox<>();
seasons.getItems().addAll("Spring", "Summer", "Fall", "Winter");

ChoiceBox一样,ComboBox需要跟踪选中的项目及其在项目列表中的索引。为此,它使用一个单独的对象,称为选择模型ComboBox类包含一个selectionModel属性来存储项目选择细节。ComboBox使用一个SingleSelectionModel类的对象作为它的选择模型。选择模型允许您从项目列表中选择一个项目,并允许您添加ChangeListener来跟踪索引和项目选择的变化。请参阅“了解选择框控件”一节,了解使用选择模型的更多详情。

ChoiceBox不同,ComboBox是可以编辑的。它的editable属性指定它是否是可编辑的。默认情况下,它不可编辑。当它可编辑时,它使用一个TextField控件来显示所选择或输入的项目。ComboBox类的editor属性存储了TextField的引用,如果组合框不可编辑,则为null,如下面的代码所示:

ComboBox<String> breakfasts = new ComboBox<>();

// Add some items to choose from
breakfasts.getItems().addAll("Apple", "Banana", "Strawberry");

// By making the control editable, let users enter an item
breakfasts.setEditable(true);

ComboBox有一个value属性,存储当前选择或输入的值。注意,当用户在可编辑的组合框中输入值时,输入的字符串被转换为组合框的项目类型T。如果项目类型不是字符串,则需要一个StringConverter<T>String值转换为类型T。我将很快给出一个例子。

您可以为组合框设置提示文本,当控件可编辑、没有焦点且其value属性为null时,将显示该提示文本。提示文本存储在promptText属性中,该属性属于StringProperty类型,如以下代码所示:

breakfasts.setPromptText("Select/Enter an item"); // Set a prompt text

ComboBox类包含一个placeholder属性,它存储一个Node引用。当项目列表为空或null时,弹出区域显示占位符节点。下面的代码片段将一个Label设置为占位符:

Label placeHolder = new Label("List is empty.\nPlease enter an item");
breakfasts.setPlaceholder(placeHolder);

清单 12-12 中的程序创建了两个ComboBox控件:seasonsbreakfasts。包含季节列表的组合框不可编辑。包含早餐项目列表的组合框是可编辑的。图 12-20 显示了当用户选择一个季节并输入一个早餐项目,甜甜圈,它不在早餐项目列表中时的屏幕截图。一个Label控件显示用户选择。当你在早餐组合框中输入一个新值时,你需要改变焦点,按回车键,或者打开弹出列表刷新消息Label

img/336502_2_En_12_Fig20_HTML.jpg

图 12-20

两个ComboBox控件:一个不可编辑,一个可编辑

// ComboBoxTest.java
// ... find in the book's download area.

Listing 12-12Using ComboBox Controls

检测组合框中的值变化

检测不可编辑的组合框中的项目变化很容易通过向其选择模型的selectedIndexselectedItem属性添加一个ChangeListener来执行。详情请参考“了解选择框控件”一节。

您仍然可以对selectedItem属性使用ChangeListener来检测可编辑组合框中的值何时改变,方法是从项目列表中选择或输入新值。当您输入一个新值时,selectedIndex属性不会改变,因为输入的值不在项目列表中。

有时,当组合框中的值发生变化时,您需要执行一个操作。您可以通过添加一个ActionEvent处理程序来做到这一点,当值以任何方式改变时,就会触发这个处理程序。您可以通过以编程方式设置它、从项列表中选择或输入新值来实现这一点,如下面的代码所示:

ComboBox<String> list = new ComboBox<>();
list.setOnAction(e -> System.out.println("Value changed"));

在可编辑的组合框中使用域对象

在可编辑的ComboBox<T>中,如果T不是String,你必须将converter属性设置为有效的StringConverter<T>。它的toString(T object)方法用于将 item 对象转换为字符串,以在弹出列表中显示。它的fromString(String s)方法被调用来将输入的字符串转换成 item 对象。用从输入的字符串转换的 item 对象更新value属性。如果输入的字符串不能转换为 item 对象,则value属性不会更新。

清单 12-13 中的程序展示了如何在一个组合框中使用一个StringConverter,该组合框在其条目列表中使用域对象。ComboBox使用了Person对象。如清单 12-11 所示的PersonStringConverter类被用作StringConverter。您可以在ComboBox中以姓氏、名字或名字的格式输入姓名,然后按 enter 键。输入的名称将被转换成一个Person对象并显示在Label中。程序忽略名称格式中的错误检查。例如,如果您输入 Kishori 作为名称,它会在Label中显示 null,Kishori。程序向选择模型的selectedItemselectedIndex属性添加一个ChangeListener来跟踪选择的变化。请注意,当您在ComboBox中输入一个字符串时,不会报告selectedIndex属性的变化。ComboBoxActionEvent处理程序用于保持组合框中的值和Label中的文本同步。

// ComboBoxWithConverter.java
// ... find in the book's download area.

Listing 12-13Using a StringConverter in a ComboBox

自定义弹出列表的高度

默认情况下,ComboBox在弹出列表中只显示十个项目。如果项目数超过十个,弹出列表会显示滚动条。如果项目数少于 10 个,弹出列表的高度会缩短,以便只显示可用的项目。ComboBoxvisibleRowCount属性控制弹出列表中可见的行数,如以下代码所示:

ComboBox<String> states = new ComboBox<>();
...
// Show five rows in the popup list
states.setVisibleRowCount(5);

使用节点作为组合框中的项目

组合框有两个区域:

  • 显示选定项目的按钮区域

  • 显示项目列表的弹出区域

两个区域都使用ListCells来显示项目。一个ListCell就是一个Cell。一个Cell是一个Labeled控件,用来显示某种形式的内容,可能有文本、图形或者两者都有。弹出区域是一个ListView,它包含列表中每个条目的一个ListCell实例。我将在下一节讨论ListView

组合框项目列表中的元素可以是任何类型,包括Node类型。不建议将Node类的实例直接添加到项目列表中。当节点用作项目时,它们会作为图形添加到单元格中。场景图形需要遵循一个节点不能同时在两个地方显示的规则。也就是说,一个节点一次只能在一个容器中。当从项目列表中选择一个节点时,该节点从弹出的ListView单元格中移除并添加到按钮区域。当弹出窗口再次显示时,所选节点不会显示在列表中,因为它已经显示在按钮区域中。为了避免显示中的这种不一致,请避免将节点直接用作组合框中的项。

图 12-21 显示了使用以下代码片段创建的组合框的三个视图。注意,代码添加了三个HBox实例,它是条目列表中的一个节点。标有#1 的图显示了第一次打开时的弹出列表,您可以正确地看到所有三个项目。在选择了第二个项目后,会出现标记为#2 的图,您会在按钮区域看到正确的项目。此时,列表中的第二个项目,一个矩形的HBox,被从ListView的单元格中移除,并添加到按钮区域的单元格中。标有#3 的图显示了第二次打开时的弹出列表。此时,列表中缺少第二个项目,因为它已经被选中。这个问题在前一段已经讨论过了。

img/336502_2_En_12_Fig21_HTML.jpg

图 12-21

项目列表中带有节点的组合框的三个视图

Label shapeLbl = new Label("Shape:");
ComboBox<HBox> shapes = new ComboBox<>();
shapes.getItems().addAll(new HBox(new Line(0, 10, 20, 10), new Label("Line")),
                new HBox(new Rectangle(0, 0, 20, 20), new Label("Rectangle")),
                new HBox(new Circle(20, 20, 10), new Label("Circle")));

您可以修复将节点用作项目时出现的显示问题。解决方案是在列表中添加非节点项,并提供一个单元工厂,以便在单元工厂中创建所需的节点。您需要确保非节点项将提供足够的信息来创建您想要插入的节点。下一节将解释如何使用细胞工厂。

组合框中使用单元格工厂

ComboBox类包含一个cellFactory属性,声明如下:

public ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactory;

Callbackjavafx.util包中的一个接口。它有一个call()方法,接受类型为P的参数并返回类型为R的对象,如下面的代码所示:

public interface Callback<P,R> {
        public R call(P param);
}

属性cellFactory的声明声明它存储了一个Callback对象,该对象的call()方法接收一个ListView<T>并返回一个ListCell<T>。在call()方法中,创建一个ListCell<T>类的实例,并覆盖Cell类的updateItem(T item, boolean empty)方法来填充单元格。

让我们使用一个单元格工厂来显示组合框的按钮区域和弹出区域中的节点。清单 12-14 将是我们的起点。它声明了一个从ListCell<String>类继承而来的StringShapeCell类。您需要在其自动调用的updateItem()方法中更新其内容。该方法接收项目,在本例中是String,以及一个boolean参数,指示单元格是否为空。在方法内部,首先调用超类中的方法。您从字符串参数中派生出一个形状,并在单元格中设置文本和图形。该形状被设置为图形。getShape()方法从String返回一个Shape

// StringShapeCell.java
package com.jdojo.control;

import javafx.scene.control.ListCell;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;

public class StringShapeCell extends ListCell<String> {
        @Override
        public void updateItem(String item, boolean empty) {
                // Need to call the super first
                super.updateItem(item, empty);

                // Set the text and graphic for the cell
                if (empty) {
                        setText(null);
                        setGraphic(null);
                } else {
                        setText(item);
                        Shape shape = this.getShape(item);
                        setGraphic(shape);
                }
        }

        public Shape getShape(String shapeType) {
                Shape shape = null;
                switch (shapeType.toLowerCase()) {
                        case "line":
                                shape = new Line(0, 10, 20, 10);
                                break;
                        case "rectangle":
                                shape = new Rectangle(0, 0, 20, 20);
                                break;
                        case "circle":
                                shape = new Circle(20, 20, 10);
                                break;
                        default:
                                shape = null;
                }
                return shape;
        }
}

Listing 12-14A Custom ListCell That Displays a Shape and Its Name

下一步是创建一个Callback类,如清单 12-15 所示。这个清单中的程序非常简单。它的call()方法返回一个StringShapeCell类的对象。这个类将充当ComboBox的细胞工厂。

// ShapeCellFactory.java
package com.jdojo.control;

import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;

public class ShapeCellFactory implements Callback<ListView<String>, ListCell<String>> {
        @Override
        public ListCell<String> call(ListView<String> listview) {
                return new StringShapeCell();
        }
}

Listing 12-15A Callback Implementation for Callback<ListView<String>, ListCell<String>>

清单 12-16 中的程序展示了如何在组合框中使用自定义单元格工厂和按钮单元格。程序很简单。它创建了一个包含三个String项的组合框。它将ShapeCellFactory的一个对象设置为单元格工厂,如下面的代码所示:

// Set the cellFactory property
shapes.setCellFactory(new ShapeCellFactory());

在这种情况下,设置细胞工厂是不够的。它只会解决在弹出区域显示形状的问题。当您选择一个形状时,它会在按钮区域显示String项,而不是形状。为了确保您在选择列表中看到相同的项目,在您选择一个项目后,您需要设置buttonCell属性,如下面的代码所示:

// Set the buttonCell property
shapes.setButtonCell(new StringShapeCell());

注意在buttonCell属性和ShapeCellFactory类中使用了StringShapeCell类。

运行清单 12-16 中的程序。您应该能够从列表中选择一个形状,并且该形状应该正确显示在组合框中。图 12-22 显示了组合框的三视图。

img/336502_2_En_12_Fig22_HTML.jpg

图 12-22

带有细胞工厂的组合框的三视图

// ComboBoxCellFactory.java
package com.jdojo.control;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

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

        @Override
        public void start(Stage stage) {
                Label shapeLbl = new Label("Shape:");
                ComboBox<String> shapes = new ComboBox<>();
                shapes.getItems().addAll("Line", "Rectangle", "Circle");

                // Set the cellFactory property
                shapes.setCellFactory(new ShapeCellFactory());

                // Set the buttonCell property
                shapes.setButtonCell(new StringShapeCell());

                HBox root = new HBox(shapeLbl, shapes);
                root.setStyle("-fx-padding: 10;" +
                              "-fx-border-style: solid inside;" +
                              "-fx-border-width: 2;" +
                              "-fx-border-insets: 5;" +
                              "-fx-border-radius: 5;" +
                              "-fx-border-color: blue;");

                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Using CellFactory in ComboBox");
                stage.show();
        }
}

Listing 12-16Using a Cell Factory in a Combo Box

在组合框中使用自定义单元格工厂和按钮单元格,可以让您非常方便地自定义弹出列表和所选项的外观。如果使用单元格工厂对你来说看起来很难或者很困惑,请记住单元格是一个Labeled控件,你是在updateItem()方法内设置那个Labeled控件中的文本和图形。因为ComboBox控件需要给你一个机会在它需要的时候创建一个单元格,所以Callback接口开始发挥作用。否则,您必须知道要创建多少个单元以及何时创建。没什么更多的了。

ComboBoxBase类提供了四个也可以与ComboBox一起使用的属性:

  • onShowing

  • onShown

  • onHiding

  • onHidden

这些属性属于类型ObjectProperty<EventHandler<Event>>。您可以为这些属性设置一个事件处理程序,在弹出列表显示之前、显示之后、隐藏之前和隐藏之后都会调用该事件处理程序。例如,当您想在弹出列表显示之前定制它时,onShowing事件处理程序非常方便。

用 CSS 设计组合框的样式

一个ComboBox的默认 CSS 样式类名是combo-box。一个组合框包含多个 CSS 子结构,如图 12-23 所示。

img/336502_2_En_12_Fig23_HTML.png

图 12-23

组合框的子结构,可以使用 CSS 单独设置样式

子结构的 CSS 名称是

  • arrow-button

  • list-cell

  • text-input

  • combo-box-popup

一个arrow-button包含一个名为arrow的子结构。arrow-buttonarrow都是StackPane的实例。list-cell区域代表用于在不可编辑的组合框中显示选中项目的ListCelltext-input区域是用于在可编辑的组合框中显示选中或输入的项目的TextFieldcombo-box-popup是点击按钮时显示弹出列表的Popup控件。它有两个子结构:list-viewlist-celllist-view是显示项目列表的ListView控件,list-cell代表ListView中的每个单元格。以下 CSS 样式定制了ComboBox的一些子结构的外观:

/* The ListCell that shows the selected item in a non-editable ComboBox */
.combo-box .list-cell {
        -fx-background-color: yellow;
}

/* The TextField that shows the selected item in an editable ComboBox */
.combo-box .text-input {
        -fx-background-color: yellow;
}

/* Style the arrow button area */
.combo-box .arrow-button {
        -fx-background-color: lightgray;
}

/* Set  the text color in the popup list for ComboBox to blue */
.combo-box-popup .list-view .list-cell {
        -fx-text-fill: blue;
}

了解 ListView 控件

ListView用于允许用户从项目列表中选择一个或多个项目。ListView中的每一项都由一个可以定制的ListCell类的实例来表示。ListView中的项目列表可以包含任何类型的对象。ListView是一个参数化类。参数类型是列表中项目的类型。如果您想在一个ListView中存储混合类型的项目,您可以使用它的类型,如下面的代码所示:

// Create a ListView for any type of items
ListView<Object> seasons = new ListView<>();

// Instead create a ListView for String items
ListView<String> seasons = new ListView<>();

您可以在创建ListView时指定列表项,如以下代码所示:

ObservableList<String> seasonList = FXCollections.<String>observableArrayList(
        "Spring", "Summer", "Fall", "Winter");
ListView<String> seasons = new ListView<>(seasonList);

在创建了一个ListView之后,您可以使用items属性将项目添加到它的项目列表中,该属性属于ObjectProperty<ObservableList<T>>类型,其中TListView的类型参数,如下面的代码所示:

ListView<String> seasons = new ListView<>();
seasons.getItems().addAll("Spring", "Summer", "Fall", "Winter");

设置它的首选宽度和高度,这通常不是你想要的宽度和高度。如果控件提供了一个像visibleItemCount这样的属性,这将有助于开发人员。不幸的是,ListView API 不支持这样的属性。您需要在代码中将它们设置为合理的值,如下所示:

// Set preferred width = 100px and height = 120px
seasons.setPrefSize(100, 120);

如果显示项目所需的空间大于可用空间,则会自动添加一个垂直滚动条、一个水平滚动条或两者都添加。

ListView类包含一个placeholder属性,它存储一个Node引用。当项目列表为空或null时,ListView的列表区显示占位符节点。下面的代码片段将一个Label设置为占位符:

Label placeHolder = new Label("No seasons available for selection.");
seasons.setPlaceholder(placeHolder);

ListView提供滚动功能。使用scrollTo(int index)scrollTo(T item)方法滚动到列表中指定的indexitem。如果指定的索引或项目尚不可见,则使其可见。当使用scrollTo()方法或用户进行滚动时,ListView类触发一个ScrollToEvent。您可以使用setOnScrollTo()方法设置一个事件处理程序来处理滚动。

使用ListCell类的实例显示ListView中的每个项目。本质上,ListCell是一个能够显示文本和图形的标签控件。ListCell的几个子类为ListView项目提供了自定义外观。ListView让您将Callback对象指定为单元格工厂,它可以创建自定义列表单元格。一个ListView不需要创建和项目数量一样多的ListCell对象。它只能有和屏幕上可见项目一样多的ListCell对象。当项目滚动时,它可以重用ListCell对象来显示不同的项目。图 12-24 显示了ListCell相关类的类图。

img/336502_2_En_12_Fig24_HTML.jpg

图 12-24

ListCell相关的类的类图

单元格在不同类型的控件中用作构造块。例如,ListViewTreeViewTableView控件以某种形式使用单元格来显示和编辑它们的数据。Cell类是所有单元格的超类。您可以覆盖它的updateItem(T object, boolean empty),完全控制单元格的填充方式。当单元格中的项需要更新时,这些控件会自动调用此方法。Cell类声明了几个有用的属性:editableeditingemptyitemselected。当Cell为空时,这意味着它不与任何数据项相关联,其empty属性为真。

IndexedCell类添加了一个index属性,它是底层模型中项的索引。假设一个ListView使用一个ObservableList作为模型。ObservableList中第二项的列表单元格的索引为 1(索引从 0 开始)。单元索引便于基于单元的索引来定制单元,例如,在奇数和偶数索引单元处对单元使用不同的颜色。当单元格为空时,其索引为–1。

列表视图的方向

ListView中的项目可以垂直排列成一列(默认)或水平排列成一行。它由orientation属性控制,如下面的代码所示:

// Arrange list of seasons horizontally
seasons.setOrientation(Orientation.HORIZONTAL);

图 12-25 显示了ListView的两个实例:一个使用垂直方向,一个使用水平方向。请注意,奇数和偶数行或列具有不同的背景颜色。这是ListView的默认外观。您可以使用 CSS 来更改外观。请参考“用 CSS 样式化 ListView”一节了解详细信息。

img/336502_2_En_12_Fig25_HTML.png

图 12-25

具有相同项目但不同方向的两个ListView实例

列表视图中选择模型

ListView有一个选择模型,存储其项目的选择状态。它的selectionModel属性存储选择模型的引用。默认情况下,它使用了一个MultipleSelectionModel类的实例。但是,您可以使用自定义选择模型,这是很少需要的。选择模型可以配置为在两种模式下工作:

  • 单一选择模式

  • 多重选择模式

在单一选择模式下,一次只能选择一个项目。如果选择了某个项目,则会取消选择之前选择的项目。默认情况下,ListView支持单选模式。可以使用鼠标或键盘选择项目。您可以使用鼠标单击来选择项目。使用键盘选择项目要求ListView有焦点。您可以使用垂直ListView中的上/下箭头和水平ListView中的左/右箭头来选择项目。

在多重选择模式下,一次可以选择多个项目。仅使用鼠标可让您一次仅选择一个项目。单击一个项目会选择该项目。按住 Shift 键单击一个项目会选择所有连续的项目。按住 Ctrl 键单击一个项目会选择一个取消选择的项目,并取消选择一个选定的项目。您可以使用上/下或左/右箭头键进行导航,并使用 Ctrl 键和空格键或 Shift 键和空格键来选择多个项目。如果您希望ListView在多重选择模式下运行,您需要设置其选择模型的selectionMode属性,如以下代码所示:

// Use multiple selection mode
seasons.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

// Set it back to single selection mode, which is the default for a ListView
seasons.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);

MultipleSelectionModel类继承自SelectionModel类,后者包含selectedIndexselectedItem属性。

如果没有选择,selectedIndex属性为–1。在单选模式下,它是当前选定项的索引。在多重选择模式下,它是最后一个选定项目的索引。在多重选择模式下,使用getSelectedIndices()方法返回一个只读的ObservableList<Integer>,其中包含所有选中项目的索引。如果您对监听ListView中的选择变化感兴趣,您可以将ChangeListener添加到selectedIndex属性中,或者将ListChangeListener添加到由getSelectedIndices()方法返回的ObservableList中。

如果没有选择,selectedItem属性为null。在单选模式下,它是当前选定的项目。在多重选择模式下,它是最后选择的项目。在多重选择模式下,使用getSelectedItems()方法,该方法返回一个包含所有选中项目的只读ObservableList<T>。如果您有兴趣监听一个ListView中的选择变化,您可以向selectedItem属性添加一个ChangeListener,或者向由getSelectedItems()方法返回的ObservableList<T>添加一个ListChangeListener

ListView的选择模型包含了几种以不同方式选择项目的方法:

  • selectAll()方法选择所有项目。

  • selectFirst()selectLast()方法分别选择第一项和最后一项。

  • selectIndices(int index, int... indices)方法选择指定索引处的项目。有效范围之外的索引将被忽略。

  • selectRange(int start, int end)方法选择从start索引(含)到end索引(不含)的所有索引。

  • clearSelection()和 c learSelection(int index)方法分别清除所有选区和指定index处的选区。

清单 12-17 中的程序演示了如何使用ListView的选择模型进行选择并监听选择变化事件。图 12-26 显示了运行这段代码产生的窗口。运行应用程序,使用鼠标或窗口上的按钮选择ListView中的项目。选择的详细信息显示在底部。

img/336502_2_En_12_Fig26_HTML.jpg

图 12-26

一个带有几个按钮进行选择的ListView

// ListViewSelectionModel.java
// ... find in the book's download area.

Listing 12-17Using a ListView Selection Model

使用列表视图中的细胞工厂

ListView中的每一项都显示在ListCell的一个实例中,这是一个Labeled控件。回想一下,Labeled控件包含文本和图形。ListView类包含一个cellFactory属性,允许您为其项目使用自定义单元格。房产类型为ObjectProperty<Callback<ListView<T>,ListCell<T>>>。对ListView的引用被传递给Callback对象的call()方法,它返回一个ListCell类的实例。在一个大的ListView中,比如说 1000 个物品,从细胞工厂返回的ListCell可能会被重复使用。控件只需要创建一定数量的可见单元格。在滚动时,它可以重用视图之外的单元格来显示新可见的项目。ListCellupdateItem()方法接收新项目的引用。

默认情况下,ListView调用其项目的toString()方法,并在其单元格中显示字符串。在您的自定义ListCellupdateItem()方法中,您可以填充单元格的文本和图形,以根据单元格中的项目显示单元格中的任何内容。

Tip

在上一节中,您为组合框的弹出列表使用了自定义单元格工厂。组合框中的弹出列表使用了一个ListView。因此,在ListView中使用自定义单元格工厂与前面组合框部分中讨论的一样。

清单 12-18 中的程序展示了如何使用自定义单元格工厂来显示Person项的格式化名称。图 12-27 显示了运行代码后的结果窗口。程序中的代码片段创建并设置一个定制的单元工厂。ListCellupdateItem()方法格式化Person对象的名称,并添加一个序列号,该序列号是单元格的索引加 1。

img/336502_2_En_12_Fig27_HTML.jpg

图 12-27

一个ListView使用定制的单元格工厂在其项目列表中显示Person对象

// ListViewDomainObjects.java
// ... find in the book's download area.

Listing 12-18Using a Custom Cell Factory for ListView

使用可编辑的列表视图

ListView控件提供了许多定制,其中之一是它允许用户编辑项目的能力。在编辑ListView之前,您需要为其设置两个属性:

  • ListVieweditable属性设置为 true。

  • ListViewcellFactory属性设置为产生可编辑ListCell的单元格工厂。

选择一个单元格,然后单击开始编辑。或者,当单元格获得焦点时,按空格键开始编辑。如果一个ListView是可编辑的并且有一个可编辑的单元格,你也可以使用ListViewedit(int index)方法在指定的index编辑单元格中的项目。

Tip

ListView类包含一个只读的editingIndex属性。它的值是正在编辑的项目的索引。如果没有编辑任何项目,则其值为–1。

JavaFX 提供了细胞工厂,允许您使用TextFieldChoiceBoxComboBoxCheckBox编辑ListCell。您可以创建自定义单元格工厂,以其他方式编辑单元格。作为ListView中的列表单元格,TextFieldListCellChoiceBoxListCellComboBoxListCellCheckBoxListCell类的实例提供编辑支持。这些类包含在javafx.scene.control.cell包中。

使用文本字段编辑列表视图

TextFieldListCell的一个实例是一个ListCell,当项目没有被编辑时,它在Label中显示一个项目,当项目被编辑时,它在TextField中显示一个项目。如果你想编辑一个域对象到一个ListView,你将需要使用一个StringConverter来促进双向转换。TextFieldListCell类的forListView()静态方法返回一个配置为与String项一起使用的单元工厂。以下代码片段显示了如何将TextField设置为ListView的单元格编辑器:

ListView<String> breakfasts = new ListView<>();
...
breakfasts.setEditable(true);

// Set a TextField as the editor
Callback<ListView<String>, ListCell<String>> cellFactory =
         TextFieldListCell.forListView();
breakfasts.setCellFactory(cellFactory);

下面的代码片段展示了如何使用包含Person对象的ListView的转换器将TextField设置为单元格编辑器。代码中使用的转换器如清单 12-11 所示。converter 对象将用于将Person对象转换为String进行显示,并将String转换为Person对象进行编辑。

ListView<Person> persons = new ListView<>();
...
persons.setEditable(true);

// Set a TextField as the editor.
// Need to use a StringConverter for Person objects.
StringConverter<Person> converter = new PersonStringConverter();
Callback<ListView<Person>, ListCell<Person>> cellFactory
        = TextFieldListCell.forListView(converter);
persons.setCellFactory(cellFactory);

清单 12-19 中的程序展示了如何编辑TextField中的ListView项。它使用了一个域对象(Person)的ListView和一个String对象的ListView。运行程序后,双击两个ListView中的任意项目开始编辑。完成编辑后,按 Enter 键提交更改。

// ListViewEditing.java
// ... find in the book's download area.

Listing 12-19Using an Editable ListView

使用选择框 / 组合框编辑列表视图

ChoiceBoxListCell的一个实例是一个ListCell,当项目没有被编辑时,它在Label中显示一个项目,当项目被编辑时,它在ChoiceBox中显示一个项目。如果你想编辑一个域对象到一个ListView,你将需要使用一个StringConverter来促进双向转换。您需要提供要在选择框中显示的项目列表。使用ChoiceBoxListCell类的forListView()静态方法创建一个细胞工厂。以下代码片段显示了如何将选择框设置为ListView的单元格编辑器:

ListView<String> breakfasts = new ListView<>();
...
breakfasts.setEditable(true);

// Set a cell factory to use a ChoiceBox for editing
ObservableList<String> items =
        FXCollections.<String>observableArrayList(
        "Apple", "Banana", "Donut", "Hash Brown");
breakfasts.setCellFactory(ChoiceBoxListCell.forListView(items));

清单 12-20 中的程序使用一个选择框来编辑ListView中的项目。双击单元格中的项目开始编辑。在编辑模式下,单元格会变成一个选择框。单击箭头显示要选择的项目列表。使用组合框进行编辑类似于使用选择框。

// ListViewChoiceBoxEditing.java
// ... find in the book's download area.

Listing 12-20Using a ChoiceBox for Editing Items in a ListView

使用复选框编辑 ListView

CheckBoxListCell类提供了使用复选框编辑ListCell的能力。它在单元格中绘制一个复选框,可以选择或取消选择。注意,当使用复选框编辑ListView项目时,复选框的第三种状态不确定状态不可选择。

使用复选框编辑ListView项略有不同。您需要为ListView中的每一项向CheckBoxListCell类提供一个ObservableValue<Boolean>对象。在内部,可观察值被双向绑定到复选框的选定状态。当用户使用复选框选择或取消选择ListView中的一个项目时,相应的ObservableValue对象被更新为 true 或 false 值。如果您想知道哪个项目被选中,您将需要保存对ObservableValue对象的引用。

让我们使用复选框重做我们之前的早餐示例。下面的代码片段创建了一个映射,并将所有条目作为一个键和一个对应的值为 false 的ObservableValue条目添加进来。使用 false 值,您希望指示最初将取消选择这些项目:

Map<String, ObservableValue<Boolean>> map = new HashMap<>();
map.put("Apple", new SimpleBooleanProperty(false));
map.put("Banana", new SimpleBooleanProperty(false));
map.put("Donut", new SimpleBooleanProperty(false));
map.put("Hash Brown", new SimpleBooleanProperty(false));

现在,您创建一个可编辑的ListView,将地图中的所有关键点作为其项目:

ListView<String> breakfasts = new ListView<>();
breakfasts.setEditable(true);

// Add all keys from the map as items to the ListView
breakfasts.getItems().addAll(map.keySet());

下面的代码片段创建了一个Callback对象。它的call()方法返回传递给call()方法的指定itemObservableValue对象。CheckBoxListCell类会自动调用这个对象的call()方法:

Callback<String, ObservableValue<Boolean>> itemToBoolean =
    (String item) -> map.get(item);

现在是时候为ListView创建和设置一个细胞工厂了。CheckBoxListCell类的forListView()静态方法接受一个Callback对象作为参数。如果您的ListView包含域对象,您也可以使用下面的代码为这个方法提供一个StringConverter:

// Set the cell factory
breakfasts.setCellFactory(CheckBoxListCell.forListView(itemToBoolean));

当用户使用复选框选择或取消选择项目时,地图中相应的ObservableValue将被更新。要知道ListView中的项目是否被选中,您需要查看该项目的ObservableValue对象中的值。

清单 12-21 中的程序展示了如何使用复选框来编辑ListView中的项目。图 12-28 显示了运行代码后的结果窗口。使用鼠标选择项目。按下打印选择按钮在标准输出上打印所选项目。

img/336502_2_En_12_Fig28_HTML.jpg

图 12-28

带有用于编辑其项目的复选框的ListView

// ListViewCheckBoxEditing.java
// ... find in the book's download area.

Listing 12-21Using a Check Box to Edit ListView Items

编辑列表视图时处理事件

可编辑的ListView触发三种事件:

  • 编辑开始时的editStart事件

  • 提交编辑值时的editCommit事件

  • 取消编辑时的editcancel事件

ListView类定义了一个ListView.EditEvent<T>静态内部类来表示与编辑相关的事件对象。它的getIndex()方法返回被编辑项目的索引。getNewValue()方法返回新的输入值。getSource()方法返回触发事件的ListView的引用。ListView类提供了onEditStartonEditCommitonEditCancel属性来设置这些方法的事件处理程序。

以下代码片段将一个editStart事件处理程序添加到一个ListView中。处理程序打印正在编辑的索引和新的项目值:

ListView<String> breakfasts = new ListView<>();
...
breakfasts.setEditable(true);
breakfasts.setCellFactory(TextFieldListCell.forListView());

// Add an editStart event handler to the ListView
breakfasts.setOnEditStart(e ->
        System.out.println("Edit Start: Index=" + e.getIndex() +
                           ", item  = " + e.getNewValue()));

清单 12-22 包含了一个完整的程序来展示如何在ListView中处理与编辑相关的事件。运行程序,双击一个项目开始编辑。更改值后,按 Enter 键提交编辑,或按 Esc 键取消编辑。与编辑相关的事件处理程序在标准输出中打印消息。

// ListViewEditEvents.java
// ... find in the book's download area.

Listing 12-22Handling Edit-Related Events in a ListView

用 CSS 设计列表视图

一个ListView的默认 CSS 样式类名是list-view,对于ListCelllist-cellListView类有两个 CSS 伪类:horizontalvertical-fx-orientation CSS 属性控制ListView的方向,可以设置为水平垂直

您可以像设计任何其他控件一样设计ListView的样式。每个项目都显示在一个ListCell实例中。ListCell提供了几个 CSS 伪类:

  • empty

  • filled

  • selected

  • odd

  • even

当单元格为空时,empty伪类适用。当单元格不为空时,filled伪类适用。当单元格被选中时,selected伪类适用。oddeven伪类分别应用于奇数和偶数索引的单元格。代表第一项的单元格的索引为 0,它被视为偶数单元格。

以下 CSS 样式将突出显示褐色的偶数单元格和浅灰色的奇数单元格:

.list-view .list-cell:even {
    -fx-background-color: tan;
}

.list-view .list-cell:odd {
    -fx-background-color: lightgray;
}

开发人员经常会问如何移除ListView中默认的替代单元格高亮显示。在modena.css文件中,所有列表单元格的默认背景颜色被设置为-fx-control-inner-background,这是一种 CSS 派生的颜色。对于所有奇数列表单元格,默认颜色设置为derive(-fx-control-inner-background,-5%)。要保持所有单元格的背景颜色相同,您需要覆盖奇数列表单元格的背景颜色,如下所示:

.list-view .list-cell:odd {
    -fx-background-color: -fx-control-inner-background;
}

这仅仅解决了问题的一半;它只负责一个ListView中正常状态下列表单元格的背景颜色。列表单元格可以有几种状态,例如,focusedselectedemptyfilled。为了彻底解决这个问题,您需要为所有州的列表单元格设置适当的背景颜色。请参考modena.css文件,获取您需要修改列表单元格背景颜色的完整状态列表。

ListCell类支持一个-fx-cell-size CSS 属性,即垂直ListView中单元格的高度和水平ListView中单元格的宽度。

列表单元格的类型可以是ListCellTextFieldListCellChoiceBoxListCellComboBoxListCellCheckBoxListCellListCell子类的默认 CSS 样式类名是text-field-list-cellchoice-box-list-cellcombo-box-list-cellcheck-box-list-cell。您可以使用这些样式类名来自定义它们的外观。以下 CSS 样式将在黄色背景的可编辑ListView中显示TextField:

.list-view .text-field-list-cell .text-field {
        -fx-background-color: yellow;
}

了解颜色选择器控件

是一个组合框样式的控件,专门为用户从标准调色板中选择颜色或使用内置颜色对话框创建颜色而设计。ColorPicker类继承自ComboBoxBase<Color>类。因此,ComboBoxBase类中声明的所有属性也适用于ColorPicker控件。我在前面的“理解组合框控件”一节中已经讨论了其中的几个属性。如果您想了解这些属性的更多信息,请参阅该部分。例如,editableonActionshowingvalue属性在ColorPicker中的工作方式与它们在组合框中的工作方式相同。一个ColorPicker有三部分:

  • ColorPicker控制

  • 调色板颜色

  • 自定义颜色对话框

一个ColorPicker控件由几个部件组成,如图 12-29 所示。您可以自定义它们的外观。颜色指示器是一个显示当前颜色选择的矩形。颜色标签以文本格式显示颜色。如果当前选择是标准颜色之一,标签将显示颜色名称。否则,它以十六进制格式显示颜色值。图 12-30 显示了一个ColorPicker控件及其调色板。

img/336502_2_En_12_Fig30_HTML.png

图 12-30

ColorPicker控件及其调色板对话框

img/336502_2_En_12_Fig29_HTML.png

图 12-29

ColorPicker控件的组件

当您单击控件中的箭头按钮时,调色板显示为弹出窗口。调色板由三个区域组成:

  • 显示一组标准颜色的调色板区域

  • 显示自定义颜色列表的自定义颜色区域

  • 打开“自定义颜色”对话框的超链接

调色板区域显示一组预定义的标准颜色。如果您单击其中一种颜色,它会关闭弹出窗口,并将所选颜色设置为ColorPicker控件的值。

自定义颜色区域显示一组自定义颜色。当您第一次打开此弹出窗口时,此区域不存在。有两种方法可以得到这个区域的颜色。您可以加载一组自定义颜色,也可以使用“自定义颜色”对话框构建和保存自定义颜色。

当点击自定义颜色…超链接时,会显示一个自定义颜色对话框,如图 12-31 所示。您可以使用“HSB”、“RGB”或“Web”选项卡,使用其中一种格式来构建自定义颜色。也可以通过从对话框左侧的颜色区域或颜色垂直栏中选择颜色来定义新颜色。当您单击颜色区域和颜色栏时,它们会显示一个小圆圈和矩形来表示新颜色。单击“保存”按钮选择控件中的自定义颜色并保存它,以便以后再次打开弹出窗口时显示在自定义颜色区域中。单击“使用”按钮为控件选择自定义颜色。

img/336502_2_En_12_Fig31_HTML.jpg

图 12-31

ColorPicker的自定义颜色对话框

使用颜色选择器控件

ColorPicker类有两个构造器。其中一个是默认构造器,另一个以初始颜色作为参数。默认构造器使用白色作为初始颜色,如下面的代码所示:

// Create a ColorPicker control with an initial color of white
ColorPicker bgColor1 = new ColorPicker();

// Create a ColorPicker control with an initial color of red
ColorPicker bgColor2 = new ColorPicker(Color.RED);

控件的value属性存储当前选择的颜色。通常,value属性是在使用控件选择颜色时设置的。但是,您也可以直接在代码中设置它,如下所示:

ColorPicker bgColor = new ColorPicker();
...
// Get the selected color
Color selectedCOlor = bgColor.getValue();

// Set the ColorPicker color to yellow
bgColor.setValue(Color.YELLOW);

ColorPicker类的getCustomColors()方法返回您保存在自定义颜色对话框中的自定义颜色列表。请注意,自定义颜色只为当前会话和当前ColorPicker控件保存。如果需要,您可以将自定义颜色保存在文件或数据库中,并在启动时加载它们。您必须编写一些代码来实现这一点:

ColorPicker bgColor = new ColorPicker();
...
// Load two custom colors
bgColor.getCustomColors().addAll(Color.web("#07FF78"), Color.web("#C2F3A7"));
...
// Get all custom colors
ObservableList<Color> customColors = bgColor.getCustomColors();

通常,当在ColorPicker中选择一种颜色时,您希望将该颜色用于其他控件。当选择一种颜色时,ColorPicker控件产生一个ActionEvent。下面的代码片段向一个ColorPicker添加了一个ActionEvent处理程序。选择一种颜色后,处理程序会将新颜色设置为矩形的填充颜色:

ColorPicker bgColor = new ColorPicker();
Rectangle rect = new Rectangle(0, 0, 100, 50);

// Set the selected color in the ColorPicker as the fill color of the Rectangle
bgColor.setOnAction(e -> rect.setFill(bgColor.getValue()));

清单 12-23 中的程序展示了如何使用ColorPicker控件。当您使用ColorPicker选择颜色时,矩形的填充颜色会更新。

// ColorPickerTest.java
// ... find in the book's download area.

Listing 12-23Using the ColorPicker Control

ColorPicker控件支持三种外观:组合框外观、按钮外观和拆分按钮外观。组合框外观是默认外观。图 12-32 分别显示了这三种外观中的一个ColorPicker

img/336502_2_En_12_Fig32_HTML.jpg

图 12-32

三看 a ColorPicker

ColorPicker类包含两个字符串内容,它们是按钮和拆分按钮外观的 CSS 样式类名。这些常量是

  • STYLE_CLASS_BUTTON

  • STYLE_CLASS_SPLIT_BUTTON

如果您想改变ColorPicker的默认外观,添加一个前面的常量作为它的样式类,如下所示:

// Use default combo-box look
ColorPicker cp = new ColorPicker(Color.RED);

// Change the look to button
cp.getStyleClass().add(ColorPicker.STYLE_CLASS_BUTTON);

// Change the look to split-button
cp.getStyleClass().add(ColorPicker.STYLE_CLASS_SPLIT_BUTTON);

Tip

可以添加STYLE_CLASS_BUTTONSTYLE_CLASS_SPLIT_BUTTON作为ColorPicker的样式类。在这种情况下,使用STYLE_CLASS_BUTTON

使用 CSS 对颜色选择器进行样式化

一个ColorPicker的默认 CSS 样式类名是color-picker。您几乎可以设计ColorPicker的每个部分,例如,颜色指示器、颜色标签、调色板对话框和自定义颜色对话框。完整参考请参考modena.css文件。

ColorPicker-fx-color-label-visible CSS 属性设置颜色标签是否可见。其默认值为 true。以下代码使颜色标签不可见:

.color-picker {
        -fx-color-label-visible: false;
}

颜色指示器是一个矩形,它有一个样式类名picker-color-rect。颜色标签是一个Label,它有一个样式类名color-picker-label。以下代码显示蓝色的颜色标签,并在颜色指示器矩形周围设置 2px 粗的黑色线条:

.color-picker .color-picker-label {
        -fx-text-fill: blue;
}

.color-picker .picker-color .picker-color-rect {
        -fx-stroke: black;
        -fx-stroke-width: 2;
}

调色板的样式类名称是color-palette。以下代码隐藏了调色板上的自定义颜色…超链接:

.color-palette .hyperlink {
        visibility: hidden;
}

了解日期选择器控件

DatePicker是一个组合框样式的控件。用户可以输入文本形式的日期,也可以从日历中选择日期。日历显示为控件的弹出窗口,如图 12-33 所示。DatePicker类继承自ComboBoxBase<LocalDate>类。在ComboBoxBase类中声明的所有属性也可用于DatePicker控件。

img/336502_2_En_12_Fig33_HTML.jpg

图 12-33

一个DatePicker控件的日历弹出窗口

弹出窗口的第一行显示月份和年份。您可以使用箭头滚动月份和年份。第二行显示周的简称。第一列显示一年中的周数。默认情况下,不显示周数列。您可以使用弹出菜单上的上下文菜单来显示它,或者您可以设置控件的showWeekNumbers属性来显示它。

日历总是显示 42 天的日期。不能选择不适用于当月的日期。每一天单元格都是DateCell类的一个实例。您可以提供一个单元格工厂来使用您的自定义单元格。稍后您将看到一个使用定制单元工厂的示例。

右键单击第一行、“周名称”、“周编号”列或“禁用日期”会显示上下文菜单。上下文菜单还包含“显示今天”菜单项,该菜单项将日历滚动到当前日期。

使用日期选择器控件

您可以使用默认构造器创建一个DatePicker;它使用null作为初始值。您也可以将一个LocalDate作为初始值传递给另一个构造器,如下面的代码所示:

// Create a DatePicker with null as its initial value
DatePicker birthDate1 = new DatePicker();

// Use September 19, 1969 as its initial value
DatePicker birthDate2 = new DatePicker(LocalDate.of(1969, 9, 19));

控件的value属性保存控件中的当前日期。您可以使用属性来设置日期。当控件有一个null值时,弹出窗口显示当月的日期。否则,弹出窗口显示当前值的月份日期,如以下代码所示:

// Get the current value
LocalDate dt = birthDate.getValue();

// Set the current value
birthDate.setValue(LocalDate.of(1969, 9, 19));

DatePicker控件提供了一个TextField以文本形式输入日期。它的editor属性存储了TextField的引用。该属性是只读的。如果不希望用户输入日期,可以将DatePickereditable属性设置为 false,如以下代码所示:

DatePicker birthDate = new DatePicker();

// Users cannot enter a date. They must select one from the popup.
birthDate.setEditable(false);

DatePicker有一个converter属性,它使用一个StringConverter将一个LocalDate转换成一个字符串,反之亦然。它的value属性将日期存储为LocalDate,它的编辑器将其显示为一个字符串,也就是格式化的日期。当您以文本形式输入日期时,转换器会将其转换为LocalDate并存储在value属性中。当您从日历弹出菜单中选择一个日期时,转换器会创建一个LocalDate来存储在value属性中,并将其转换为一个字符串来显示在编辑器中。默认转换器使用默认的Locale和年表来格式化日期。当您以文本形式输入日期时,默认转换器期望文本采用默认的Locale和年表格式。

清单 12-24 包含了一个LocalDateStringConverter类的代码,它是LocalDateStringConverter。默认情况下,它将日期格式化为MM/dd/yyyy格式。您可以在其构造器中传递不同的格式。

// LocalDateStringConverter.java
package com.jdojo.control;

import javafx.util.StringConverter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class LocalDateStringConverter extends StringConverter<LocalDate> {
        private String pattern = "MM/dd/yyyy";
        private DateTimeFormatter dtFormatter;

        public LocalDateStringConverter() {
                dtFormatter = DateTimeFormatter.ofPattern(pattern);
        }

        public LocalDateStringConverter(String pattern) {
                this.pattern = pattern;
                dtFormatter = DateTimeFormatter.ofPattern(pattern);
        }

        @Override
        public LocalDate fromString(String text) {
                LocalDate date = null;
                if (text != null && !text.trim().isEmpty()) {
                        date = LocalDate.parse(text, dtFormatter);
                }
                return date;
        }

        @Override
        public String toString(LocalDate date) {
                String text = null;
                if (date != null) {
                        text = dtFormatter.format(date);
                }
                return text;
        }
}

Listing 12-24A StringConverter to Convert a LocalDate to a String and Vice Versa

要将日期格式化为"MMMM dd, yyyy"格式,例如,2013 年 5 月 29 日,您需要创建并设置 convert,如下所示:

DatePicker birthDate = new DatePicker();
birthDate.setConverter(new LocalDateStringConverter("MMMM dd, yyyy"));

您可以将DatePicker控件配置为使用特定的时序,而不是默认时序。以下陈述将年表设定为泰国佛教年表:

birthDate.setChronology(ThaiBuddhistChronology.INSTANCE);

您可以为 JVM 的当前实例更改默认的LocaleDatePicker将使用默认的Locale的日期格式和年表:

// Change the default Locale to Canada
Locale.setDefault(Locale.CANADA);

弹出日历中的每个日期单元格都是从Cell<LocalDate>类继承而来的DateCell类的一个实例。DatePicker类的dayCellFactory属性允许您提供一个定制的日细胞工厂。这个概念与前面讨论的为ListView控件提供细胞工厂的概念相同。以下语句创建一个 day cell 工厂。它将周末单元格的文本颜色更改为蓝色,并禁用所有未来日期单元格。如果您将该日单元格工厂设置为DatePicker,弹出日历将不允许用户选择未来日期,因为您将禁用所有未来日单元格:

Callback<DatePicker, DateCell> dayCellFactory =
    new Callback<DatePicker, DateCell>() {
        public DateCell call(final DatePicker datePicker) {
            return new DateCell() {
                @Override
                public void updateItem(LocalDate item, boolean empty) {
                    // Must call super
                    super.updateItem(item, empty);
                    // Disable all future date cells
                    if (item.isAfter(LocalDate.now())) {
                        this.setDisable(true);
                    }
                    // Show Weekends in blue
                    DayOfWeek day = DayOfWeek.from(item);
                    if (day == DayOfWeek.SATURDAY ||
                            day == DayOfWeek.SUNDAY) {
                        his.setTextFill(Color.BLUE);
                    }
                }
            };
        }
   };

下面的代码片段为出生日期DatePicker控件设置了一个自定义的日单元格工厂。它还使控件不可编辑。该控件将强制用户从弹出日历中选择一个非未来日期:

DatePicker birthDate = new DatePicker();

// Set a day cell factory to disable all future day cells
// and show weekends in blue
birthDate.setDayCellFactory(dayCellFactory);

// Users must select a date from the popup calendar
birthDate.setEditable(false);

DatePicker控件的 value 属性改变时,它触发一个ActionEvent。当用户输入日期、从弹出窗口中选择日期或者以编程方式设置日期时,value属性可能会改变,如以下代码所示:

// Add an ActionEvent handler
birthDate.setOnAction(e -> System.out.println("Date changed to:" + birthDate.getValue()));

清单 12-25 有一个完整的程序显示如何使用DatePicker控件。它使用了DatePicker的大部分功能。显示如图 12-34 所示的窗口。该控件是不可编辑的,迫使用户从弹出窗口中选择一个非未来日期。

img/336502_2_En_12_Fig34_HTML.jpg

图 12-34

用于选择非未来日期的DatePicker控件

// DatePickerTest.java
// ... find in the book's download area.

Listing 12-25Using the DatePicker Control

用 CSS 设计日期选择器的样式

一个DatePicker的默认 CSS 样式类名是date-picker,对于它的弹出窗口,类名是date-picker-popup。您几乎可以为DatePicker的每个部分设置样式,例如,弹出窗口顶部区域的月-年窗格、日单元格、周数单元格和当前日单元格。完整参考请参考modena.css文件。

日单元格的 CSS 样式类名是day-cell。当前日期的日单元格的样式类名为today。以下样式以粗体显示当前日期,以蓝色显示所有日期:

/* Display current day numbers in bolder font */
.date-picker-popup > * > .today {
        -fx-font-weight: bolder;
}

/* Display all day numbers in blue */
.date-picker-popup > * > .day-cell {
    -fx-text-fill: blue;
}

了解文本输入控件

JavaFX 支持文本输入控件,允许用户使用单行或多行纯文本。我将在本节讨论TextFieldPasswordFieldTextArea文本输入控件。所有文本输入控件都继承自TextInputControl类。文本输入控件的类图请参见图 12-1 。

Tip

JavaFX 提供了名为HTMLEditor的富文本编辑控件。我将在本章后面讨论HTMLEditor

TextInputControl类包含适用于所有类型文本输入控件的属性和方法。与当前插入符号位置、移动和文本选择相关的属性和方法都在这个类中。子类添加适用于它们的属性和方法。表 12-5 列出了在TextInputControl类中声明的属性。

表 12-5

TextInputControl类中声明的属性

|

财产

|

类型

|

描述

| | --- | --- | --- | | anchor | ReadOnlyIntegerProperty | 它是文本选择的锚点。它位于所选内容中插入符号位置的另一端。 | | caretPosition | ReadOnlyIntegerProperty | 它是插入符号在文本中的当前位置。 | | editable | BooleanProperty | 如果控件是可编辑的,则为真。否则就是假的。 | | font | ObjectProperty<Font> | 这是控件的默认字体。 | | length | ReadOnlyIntegerProperty | 它是控件中的字符数。 | | promptText | StringProperty | 它是提示文本。当控件没有内容时,它显示在控件中。 | | redoable | ReadOnlyBooleanProperty | 告知是否可以重做最近的更改。 | | selectedText | ReadOnlyStringProperty | 它是控件中的选定文本。 | | selection | ReadOnlyObjectProperty <IndexRange> | 这是选定的文本索引范围。 | | text | StringProperty | 它是控件中的文本。 | | textFormatter | ObjectProperty<TextFormatter<?>> | 当前附加的文本格式化程序。 | | undoable | ReadOnlyBooleanProperty | 告知是否可以撤消最近的更改。 |

定位和移动插入符号

所有文本输入控件都提供一个插入符号。默认情况下,当控件有焦点时,插入符号是一条闪烁的垂直线。当前插入符号位置是键盘下一个输入字符的目标。插入符号位置从零开始,在第一个字符之前。位置 1 在第一个字符之后,第二个字符之前,依此类推。图 12-35 显示了有四个字符的文本输入控件中的插入符号位置。文本中的字符数决定了插入符号位置的有效范围,从零到文本长度。如果控件不包含文本,零是唯一有效的插入符号位置。

img/336502_2_En_12_Fig35_HTML.jpg

图 12-35

具有四个字符的文本输入控件中的插入符号位置

有几种方法将插入符号位置作为参数。这些方法将参数值固定在有效的插入符号位置范围内。传递有效范围之外的插入符号位置不会引发异常。例如,如果控件有四个字符,并且您希望将插入符号移动到位置 10,则插入符号将定位在位置 4。

只读的caretPosition属性包含当前插入符号的位置。使用positionCaret(int pos)方法将插入符号定位在指定的pos处。如果没有选择,那么backward()forward()方法分别将插入符号向前和向后移动一个字符。如果有选择,它们将插入符号位置移动到开头和结尾,并清除选择。home()end()方法分别将插入符号移动到第一个字符之前和最后一个字符之后,并清除选择。方法将插入符号移动到下一个单词的开头并清除选择。endOfNextWord()方法将插入符号移动到下一个单词的末尾并清除选择。方法将插入符号移动到前一个单词的开头并清除选择。

选择文本

TextInputControl类通过其属性和方法提供了丰富的 API 来处理文本选择。使用选择 API,您可以选择整个或部分文本,并获得选择信息。

selectedText属性包含所选文本的值。如果没有选择,其值为空字符串。属性包含一个保存选择的索引范围的 ??。IndexRange类的getStart()getEnd()方法分别返回选择的开始索引和结束索引,其getLength()方法返回选择的长度。如果没有选择,范围的下限和上限是相同的,它们等于caretPosition值。

anchorcaretPosition属性在文本选择中起着至关重要的作用。这些属性的值定义了选择范围。两个属性的值相同表示没有选择。任一属性可以指示选择范围的开始或结束。anchor值是选择开始时的插入符号位置。您可以通过前后移动插入符号来选择字符。例如,您可以在按住 Shift 键的同时使用向左或向右箭头键来选择一系列字符。如果在选择过程中向前移动插入符号,anchor值将小于caretPosition值。如果在选择过程中向后移动插入符号,anchor值将大于caretPosition值。图 12-36 显示了anchorcaretPosition值之间的关系。

img/336502_2_En_12_Fig36_HTML.png

图 12-36

文本输入控件的anchorcaretPosition属性之间的关系

在图 12-36 中,标记为#1 的部分显示了一个带有文本祝福的文本输入控件。caretPosition值为 1。用户通过将插入符号向前移动四个位置来选择四个字符,例如,通过按下 Shift 键和右箭头键或者通过拖动鼠标。如标记为#2 的部分所示,selectedText属性更少。anchor值为 1,caretPosition值为 5。选择属性的IndexRange为 1 到 5。

在标记为#3 的部分中,caretPosition值为 5。用户通过向后移动插入符号来选择四个字符,如标记为#4 的部分所示。如标记为#4 的部分所示,selectedText属性更少。anchor值为 5,caretPosition值为 1。选择属性的IndexRange为 1 到 5。请注意,在标记为#2 和#4 的部分中,anchorcaretPosition值不同,而selectedText和选择属性相同。

除了selection属性之外,TextInputControl还包含几个有用的与选择相关的方法:

  • selectAll()

  • deselect()

  • selectRange(int anchor, int caretPosition)

  • selectHome()

  • selectEnd()

  • extendSelection(int pos)

  • selectBackward()

  • selectForward()

  • selectPreviousWord()

  • selectEndOfNextWord()

  • selectNextWord()

  • selectPositionCaret(int pos)

  • replaceSelection(String replacement)

注意,您有一个positionCaret(int pos)方法和一个selectPositionCaret(int pos)方法。前者将插入符号定位在指定位置,并清除选定内容。后者将插入符号移动到指定的pos,如果存在,则扩展选择。如果不存在选择,它通过将当前插入符号位置作为锚点并将插入符号移动到指定的pos来形成选择。

方法用指定的替换来替换选中的文本。如果没有选定内容,则清除选定内容,并在当前插入符号位置插入指定的替换内容。

修改内容

TextInputControl类的text属性表示文本输入控件的文本内容。您可以使用setText(String text)方法更改内容,并使用getText()方法获取内容。clear()方法将内容设置为一个空字符串。

方法在指定的索引处插入指定的文本。如果指定的索引在有效范围之外(零到内容的长度),它抛出一个IndexOutOfBoundsExceptionappendText(String text)方法将指定的文本附加到内容中。deleteText()方法允许您从内容中删除一系列字符。您可以将范围指定为一个IndexRange对象或开始和结束索引。如果没有选择,deleteNextChar()deletePreviousChar()方法分别从当前插入符号位置删除下一个和上一个字符。如果有选择,他们会删除选择。如果删除成功,它们返回true。否则,它们返回false

只读的length属性表示内容的长度。当您修改内容时,它会发生变化。实际上,length值可以很大。没有直接的方法来限制文本输入控件中的字符数。我将很快介绍一个限制文本长度的例子。

剪切、复制和粘贴文本

文本输入控件使用鼠标和键盘以编程方式支持剪切、复制和粘贴功能。要使用鼠标和键盘使用这些功能,请使用您的平台支持的标准步骤。使用cut()copy()paste()方法以编程方式使用这些特性。cut()方法将当前选中的文本转移到剪贴板,并删除当前选中的文本。方法的作用是:将当前选中的文本转移到剪贴板,而不移除当前选中的文本。paste()方法用剪贴板中的内容替换当前选择。如果没有选定内容,则在当前插入符号位置插入剪贴板内容。

一个例子

清单 12-26 中的程序演示了文本输入控件的不同属性是如何变化的。显示如图 12-37 所示的窗口。该程序使用一个文本输入控件TextField来显示一行文本。通过将text属性绑定到TextField的属性,每个属性都显示在一个Label中。运行程序后,更改 name 字段中的文本,移动插入符号,并更改选择以查看TextField的属性是如何变化的。

img/336502_2_En_12_Fig37_HTML.jpg

图 12-37

使用文本输入控件的属性

// TextControlProperties.java
// ... find in the book's download area.

Listing 12-26Using the Properties of Text Input Controls

使用 CSS 样式化 TextInputControl

TextInputControl类引入了一个名为readonly的 CSS 伪类,它适用于控件不可编辑的情况。它添加了以下样式属性:

  • -fx-font

  • -fx-text-fill

  • -fx-prompt-text-fill

  • -fx-highlight-fill

  • -fx-highlight-text-fill

  • -fx-display-caret

默认情况下,-fx-font属性继承自父属性。-fx-display-caret属性的值可以是 true 或 false。如果为 true,则当控件获得焦点时会显示插入符号。否则,不显示插入符号。其默认值为 true。大多数其他属性会影响背景和文本颜色。

了解文本字段控件

TextField是一个文本输入控件。它继承自TextInputControl类。它允许用户输入单行纯文本。如果你需要一个控件来输入多行文本,使用TextArea来代替。文本中的换行符和制表符被删除。图 12-38 显示了一个带有两个TextField文本 Layne 和 Estes 的窗口。

img/336502_2_En_12_Fig38_HTML.jpg

图 12-38

带有两个TextField控件的窗口

您可以使用空的初始文本或指定的初始文本创建一个TextField,如下面的代码所示:

// Create a TextField with an empty string as initial text
TextField nameFld1 = new TextField();

// Create a TextField with "Layne Estes" as an initial text
TextField nameFld2 = new TextField("Layne Estes");

正如我已经提到的,TextFieldtext属性存储文本内容。如果您对处理一个TextField中的变化感兴趣,您需要将一个ChangeListener添加到它的文本属性中。大多数情况下,您将使用它的setText(String newText)方法来设置新文本,使用getText()方法来从中获取文本。TextField增加以下属性:

  • alignment

  • onAction

  • prefColumnCount

当有空白空间时,alignment属性决定了文本在TextField区域内的对齐方式。如果节点方向为LEFT_TO_RIGHT,则默认值为CENTER_LEFT,如果节点方向为RIGHT_TO_LEFT,则默认值为CENTER_RIGHTonAction属性是一个ActionEvent处理程序,在TextField中按回车键时调用,如下面的代码所示:

TextField nameFld = new TextField();
nameFld.setOnAction(e -> /* Your ActionEvent handler code...*/ );

prefColumnCount属性决定控件的宽度。默认情况下,其值为 12。一列的宽度足以显示一个大写字母 w。如果将它的值设置为 10,则TextField的宽度将足以显示十个字母 w,如下面的代码所示:

// Set the preferred column count to 10
nameFld.setPrefColumnCount(10);

TextField提供一个默认的上下文菜单,如图 12-39 所示,单击鼠标右键可以显示该菜单。菜单项根据上下文启用或禁用。您可以用自定义上下文菜单替换默认上下文菜单。目前,没有办法自定义默认的上下文菜单。

img/336502_2_En_12_Fig39_HTML.jpg

图 12-39

默认的上下文菜单为TextField

下面的代码片段为一个TextField设置了一个定制的上下文菜单。它显示一个菜单项,说明上下文菜单已禁用。选择菜单项没有任何作用。您需要向上下文菜单中的菜单项添加一个ActionEvent处理程序来执行一些操作。

ContextMenu cm = new ContextMenu();
MenuItem dummyItem = new MenuItem("Context menu is disabled");
cm.getItems().add(dummyItem);

TextField nameFld = new TextField();
nameFld.setContextMenu(cm);

清单 12-27 中的程序展示了如何使用TextField控件。它显示了两个TextField。它显示了ActionEvent处理程序,一个定制的上下文菜单,以及添加到TextFieldChangeListeners

// TextFieldTest.java
// ... find in the book's download area.

Listing 12-27Using the TextField Control

使用 CSS 样式化文本字段

一个TextField的默认 CSS 样式类名是text-field *。*它添加了一个-fx-alignment属性,该属性是其内容区域内文本的对齐方式。造型TextField没什么特别需要说的。

理解密码字段控件

PasswordField是一个文本输入控件。它从TextField继承而来,它的工作方式与TextField非常相似,除了它屏蔽了它的文本,也就是说,它不显示实际输入的字符。相反,它为输入的每个字符显示一个回显字符。默认回显字符是一个项目符号。图 12-40 显示了一个带有PasswordField的窗口。

img/336502_2_En_12_Fig40_HTML.jpg

图 12-40

使用PasswordField控件的窗口

PasswordField类只提供了一个构造器,它是一个无参数的构造器。您可以使用setText()getText()方法分别设置和获取PasswordField中的实际文本,如下面的代码所示。通常,您不需要设置密码文本。用户输入它。

// Create a PasswordField
PasswordField passwordFld = new PasswordField();
...
// Get the password text
String passStr = passwordFld.getText();

PasswordField覆盖了TextInputControl类的cut()copy()方法,使它们成为无操作方法。也就是说,您不能使用键盘快捷键或上下文菜单将PasswordField中的文本转移到剪贴板。

一个PasswordField的默认 CSS 样式类名是password-field。它拥有TextField的所有风格属性。它不添加任何样式属性。

了解 TextArea 控件

TextArea是一个文本输入控件。它继承自TextInputControl类。它允许用户输入多行纯文本。如果你需要一个控件来输入一行纯文本,使用TextField来代替。如果您想使用富文本,请使用HTMLEditor控件。与TextField不同,文本中的换行符和制表符被保留。在TextArea中,换行符开始一个新的段落。图 12-41 显示了带有TextFieldTextArea的窗口。用户可以在TextArea中输入多行简历。

img/336502_2_En_12_Fig41_HTML.png

图 12-41

带有TextArea控件的窗口

您可以使用以下代码创建一个带有空初始文本或指定初始文本的TextArea:

// Create a TextArea with an empty string as its initial text
TextArea resume1 = new TextArea();

// Create a TextArea an initial text
TextArea resume2 = new TextArea("Years of Experience: 19");

正如上一节已经讨论过的,TextAreatext属性存储文本内容。如果您对处理一个TextArea中的变化感兴趣,您需要向它的text属性添加一个ChangeListener。大多数时候,您将使用它的setText(String newText)方法设置新文本,使用它的getText()方法从中获取文本。

TextArea添加以下属性:

  • prefColumnCount

  • prefRowCount

  • scrollLeft

  • scrollTop

  • wrapText

prefColumnCount属性决定控件的宽度。默认情况下,其值为 32。一个列的宽度足以显示一个大写字母 w。如果将它的值设置为 80,那么TextArea的宽度将足以显示 80 个字母 Ws。以下代码实现了这一点:

// Set the preferred column count to 80
resume1.setPrefColumnCount(80);

prefRowCount属性决定控件的高度。默认情况下,它是 10。以下代码将行数设置为 20:

// Set the preferred row count to 20
resume.setPrefColumnCount(20);

如果文本超过了列数和行数,将自动显示水平和垂直滚动窗格。

TextFieldTextArea提供了一个默认的上下文菜单。请参考“理解文本输入控件”一节,了解关于如何定制默认上下文菜单的更多细节。

scrollLeftscrollTop属性是文本滚动到顶部和左侧的像素数。以下代码将其设置为 30px:

// Scroll the resume text by 30px to the top and 30 px to the left
resume.setScrollTop(30);
resume.setScrollLeft(30);

默认情况下,TextArea在其文本中遇到换行符时会开始新的一行。换行符还会在第一段之外创建一个新的段落。默认情况下,如果文本超出控件的宽度,它不会换行到下一行。属性决定了当文本超出控件的宽度时是否换行。默认情况下,其值为 false。以下代码会将默认值设置为 true:

// Wrap the text if needed
resume.setWrapText(true);

TextArea类的getParagraphs()方法返回文本中所有段落的不可修改列表。列表中的每个元素都是一个段落,是CharSequence的一个实例。返回的段落不包含换行符。下面的代码片段打印了resume TextArea中所有段落的详细信息,例如段落编号和字符数:

ObservableList<CharSequence> list = resume.getParagraphs();
int size = list.size();
System.out.println("Paragraph Count:" + size);
for(int i = 0; i < size; i++) {
        CharSequence cs = list.get(i);
        System.out.println("Paragraph #" + (i + 1) + ", Characters="  + cs.length());
        System.out.println(cs);
}

清单 12-28 中的程序展示了如何使用TextArea。它显示一个带有按钮的窗口,用于打印TextArea中文本的详细信息。

// TextAreaTest.java
// ... find in the book's download area.

Listing 12-28Using TextArea Controls

使用 CSS 对文本区域进行样式化

一个TextArea的默认 CSS 样式类名是text-area。它不会向其祖先TextInputControl中的 CSS 属性添加任何 CSS 属性。它包含scroll-panecontent子结构,分别是一个ScrollPane和一个Regionscroll-pane是当文本超出宽度或高度时出现的滚动窗格。content是显示文本的区域。

以下样式将水平和垂直滚动条策略设置为always,因此滚动条应该总是出现在TextArea中。内容区域的填充设置为 10px:

.text-area > .scroll-pane {
        -fx-hbar-policy: always;
        -fx-vbar-policy: always;
}

.text-area .content {
        -fx-padding: 10;
}

Tip

在撰写本文时,TextArea忽略了为scroll-pane子结构设置滚动条策略。

显示任务的进度

当您有一个长时间运行的任务时,您需要向用户提供一个关于任务进度的可视化反馈,以获得更好的用户体验。JavaFX 提供了两个控件来显示进度:

  • ProgressIndicator

  • ProgressBar

它们显示进度的方式不同。ProgressBar类继承自ProgressIndicator类。ProgressIndicator在圆形控件中显示进度,而ProgressBar使用水平条。ProgressBar类不添加任何属性或方法。它只是为控件使用了不同的形状。图 12-42 显示了不确定和确定状态下的ProgressIndicator。图 12-43 显示了处于不确定和确定状态的ProgressBar。这两个图在确定状态的四个实例中使用相同的进度值。

img/336502_2_En_12_Fig43_HTML.jpg

图 12-43

不确定和确定状态下的ProgressBar控制

img/336502_2_En_12_Fig42_HTML.jpg

图 12-42

不确定和确定状态下的ProgressIndicator控制

可以确定或不确定任务的当前进度。如果不能确定进度,就说处于不确定状态。如果进程是已知的,就说它处于确定的状态。ProgressIndicator类声明了两个属性:

  • indeterminate

  • progress

indeterminate属性是只读的boolean属性。如果它返回true,就意味着无法确定进度。这种状态下的一个ProgressIndicator被渲染成某种重复的动画。progress房产是一个double房产。其值表示 0%和 100%之间的进度。负值表示进度不确定。介于 0 和 1.0 之间的值表示进度介于 0%和 100%之间的确定状态。大于 1.0 的值被视为 1.0(即 100%进度)。

这两个类都提供了默认的构造器,用于创建处于不确定状态的控件,如下面的代码所示:

// Create an indeterminate progress indicator and a progress bar
ProgressIndicator indeterminateInd = new ProgressIndicator();
ProgressBar indeterminateBar = new ProgressBar();

其他接受进度值的构造器创建不确定或确定状态的控件。如果进度值为负,它们将创建处于不确定状态的控件。否则,它们会在确定状态下创建控件,如下面的代码所示:

// Create a determinate progress indicator with 10% progress
ProgressIndicator indeterminateInd = new ProgressIndicator(0.10);

// Create a determinate progress bar with 70% progress
ProgressBar indeterminateBar = new ProgressBar(0.70);

清单 12-29 中的程序显示了如何使用ProgressIndicatorProgressBar控件。点按“制作进度”按钮会使进度增加 10%。单击“完成任务”按钮,通过将进度设置为 100%来完成不确定的任务。通常,当任务进展到一个里程碑时,这些控件的progress属性由一个长期运行的任务更新。您使用了一个按钮来更新progress属性,以保持程序逻辑简单。

// ProgressTest.java
// ... find in the book's download area.

Listing 12-29Using the ProgressIndicator and ProgressBar Controls

带有 CSS 的样式渐进指示器

一个ProgressIndicator的默认 CSS 样式类名是progress-indicatorProgressIndicator支持determinateindeterminate CSS 伪类。当indeterminate属性为假时,determinate伪类适用。当indeterminate属性为真时,in determinate伪类适用。

ProgressIndicator有一个名为-fx-progress-color的 CSS 样式属性,是进度的颜色。以下样式将进度颜色设置为红色表示不确定进度,蓝色表示确定进度:

.progress-indicator:indeterminate {
        -fx-progress-color: red;
}

.progress-indicator:determinate {
        -fx-progress-color: blue;
}

ProgressIndicator包含四个子结构:

  • 一个indicator子结构,它是一个StackPane

  • 一个progress子结构,也就是一个StackPane

  • 一个percentage子结构,也就是一个Text

  • 一个tick子结构,也就是一个StackPane

您可以设计一个ProgressIndicator的所有子结构。样本代码请参考modena.css文件。

带有 CSS 的样式进度条指示器进度条

一个ProgressBar的默认 CSS 样式类名是progress-bar。它支持 CSS 样式属性:

  • -fx-indeterminate-bar-length

  • -fx-indeterminate-bar-escape

  • -fx-indeterminate-bar-flip

  • -fx-indeterminate-bar-animation-time

所有属性都适用于显示不确定进度的条形图。默认条形长度为 60px。使用-fx-indeterminate-bar-length属性指定不同的条形长度。

-fx-indeterminate-bar-escape属性为 true 时,条形起始边缘从轨道的起始边缘开始,条形尾边缘在轨道的结束边缘结束。也就是说,小节显示在轨道长度之外。当此属性为 false 时,条形在轨道长度内移动。默认值为 true。

-fx-indeterminate-bar-flip属性指示该条是只在一个方向上移动还是在两个方向上移动。默认值为 true,这意味着该条通过在每条边的末端翻转其方向而向两个方向移动。

属性是条从一个边缘到另一个边缘应该花费的时间(以秒为单位)。默认值为 2。

ProgressBar包含两个子结构:

  • 轨道底座,即StackPane

  • 一个条形子结构,它是一个区域

以下样式修改了ProgressBar控件的背景色、条和轨迹的半径,使其看起来如图 12-44 所示:

img/336502_2_En_12_Fig44_HTML.jpg

图 12-44

定制ProgressBar控件的条和轨道

.progress-bar .track  {
        -fx-background-color: lightgray;
        -fx-background-radius: 5;
}

.progress-bar .bar  {
        -fx-background-color: blue;
        -fx-background-radius: 5;
}

了解标题窗格控件

TitledPane是带标签的控件。TitledPane类继承自Labeled类。一个带标签的控件可以有文本和图形,所以它可以有一个TitledPaneTitledPane显示文本作为标题。该图形显示在标题栏中。

除了文本和图形,TitledPane还有内容,这是一个Node。通常,一组控件被放在一个容器中,该容器被添加为TitledPane的内容。TitledPane可处于折叠或展开状态。在折叠状态下,它只显示标题栏并隐藏内容。在展开状态下,它显示标题栏和内容。在其标题栏中,它显示一个箭头,指示它是展开还是折叠。单击标题栏中的任意位置可以展开或折叠内容。图 12-45 显示了处于两种状态的TitledPane及其所有零件。

img/336502_2_En_12_Fig45_HTML.png

图 12-45

处于折叠和展开状态

使用默认构造器创建一个没有标题和内容的TitledPane。您可以稍后使用setText()setContent()方法来设置它们。或者,您可以使用以下代码将标题和内容作为参数提供给其构造器:

// Create a TitledPane and set its title and content
TitledPane infoPane1 = new TitledPane();
infoPane1.setText("Personal Info");
infoPane1.setContent(new Label("Here goes the content."));

// Create a TitledPane with a title and content
TitledPane infoPane2 = new TitledPane("Personal Info", new Label("Content"));

您可以使用在Labeled类中声明的setGraphic()方法向TitledPane添加图形,如以下代码所示:

String imageStr = "resources/picture/privacy_icon.png";
URL imageUrl = getClass().getClassLoader().getResource(imageStr);
Image img = new Image(imageUrl.toExternalForm());
ImageView imgView = new ImageView(img);
infoPane2.setGraphic(imgView);

TitledPane类声明了四个属性:

  • animated

  • collapsible

  • content

  • expanded

animated属性是一个boolean属性,指示折叠和展开动作是否是动画的。默认情况下,这是真的,这些动作是动画。collapsible属性是一个boolean属性,表示TitledPane是否可以崩溃。默认情况下,它被设置为 true,并且TitledPane可以折叠。如果不希望TitledPane折叠,将该属性设置为 false。不可折叠的TitledPane在其标题栏不显示箭头。content属性是一个Object属性,存储任何节点的引用。当控件处于展开状态时,内容可见。expanded属性是一个boolean属性。当属性为 true 时,TitledPane处于展开状态。否则,它处于折叠状态。默认情况下,TitledPane处于展开状态。使用setExpanded()方法以编程方式展开和折叠TitledPane,如以下代码所示:

// Set the state to expanded
infoPane2.setExpanded(true);

Tip

如果您对处理一个TitledPane的展开和折叠事件感兴趣,可以将一个ChangeListener添加到它的expanded属性中。

通常,TitledPane控件在Accordion控件中成组使用,为了节省空间,每次只显示展开状态的组中的一个TitledPane。如果您想成组显示控件,也可以使用独立的TitledPane

Tip

回想一下,TitledPane的高度随着其展开和折叠而变化。不要在代码中设置它的最小、首选和最大高度。否则,可能会导致未指定的行为。

清单 12-30 中的程序显示了如何使用TitledPane控件。它显示一个带有TitledPane的窗口,让用户输入一个人的名字、姓氏和出生日期。

// TitledPaneTest.java
// ... find in the book's download area.

Listing 12-30Using the TitledPane Control

带有 CSS 的样式标题窗格

一个TitledPane的默认 CSS 样式类名是titled-paneTitledPane增加了两个boolean类型的样式属性:

  • -fx-animated

  • -fx-collapsible

这两个属性的默认值都是 true。-fx-animated属性指示展开和折叠动作是否是动画的。-fx-collapsible属性指示控件是否可以折叠。

TitledPane支持两种 CSS 伪类:

  • collapsed

  • expanded

当控件折叠时,collapsed伪类适用,当控件展开时,expanded伪类适用。

TitledPane包含两个子结构:

  • title

  • Content

title子结构是一个包含标题栏内容的StackPanetitle子结构包含文本和箭头按钮子结构。文本子结构是一个Label,它保存标题文本和图形。箭头按钮子结构是一个包含箭头子结构的StackPane,它也是一个StackPane。箭头子结构是一个指示器,它显示控件是处于展开状态还是折叠状态。内容子结构是一个包含控件内容的StackPane

让我们来看一个将四种不同样式应用于TitledPane控件的效果的例子,如下面的代码所示:

/* #1 */
.titled-pane > .title  {
        -fx-background-color: lightgray;
        -fx-alignment: center-right;
}

/* #2 */
.titled-pane > .title > .text {
        -fx-font-size: 14px;
        -fx-underline: true;
}

/* #3 */
.titled-pane > .title > .arrow-button > .arrow {
        -fx-background-color: blue;
}

/* #4 */
.titled-pane > .content {
        -fx-background-color: burlywood;
        -fx-padding: 10;
}

样式#1 将标题的背景色设置为浅灰色,并将图形和标题放在标题栏的中央右侧。样式#2 将标题文本的字体大小改为 14px 并加下划线。在撰写本文时,使用-fx-text-fill属性设置标题的文本颜色不起作用,并且在TitledPane上设置-fx-text-fill属性本身也会影响内容的文本颜色。样式#3 将箭头的背景色设置为蓝色。样式#4 设置内容区域的背景颜色和填充。图 12-46 显示应用前述样式后与图 12-45 相同的窗口。

img/336502_2_En_12_Fig46_HTML.jpg

图 12-46

将样式应用到TitledPane的效果

了解手风琴控件

Accordion是简单控件。它显示一组TitledPane控件,其中一次只有一个控件处于展开状态。图 12-47 显示了一个带有Accordion的窗口,包含三个TitledPanesTitledPane将军被扩大了。地址和电话TitledPane都被折叠了。

img/336502_2_En_12_Fig47_HTML.png

图 12-47

一个Accordion带三个TitledPanes

Accordion类只包含一个构造器(无参数构造器)来创建它的对象:

// Create an Accordian
Accordion root = new Accordion();

Accordion将它的TitledPane控件列表存储在一个ObservableList<TitledPane>中。getPanes()方法返回TitledPane的列表。使用该列表向Accordion添加或移除任何TitledPane,如以下代码所示:

TitledPane generalPane = new TitledPane();
TitledPane addressPane = new TitledPane();
TitledPane phonePane = new TitledPane();
...
Accordion root = new Accordion();
root.getPanes().addAll(generalPane, addressPane, phonePane);

Accordion类包含一个expandedPane属性,存储当前展开的TitledPane的引用。默认情况下,Accordion以折叠状态显示其所有的TitledPanes,该属性设置为null。点击TitledPane的标题栏或使用setExpandedPane()方法展开TitledPane。如果您对扩展的TitledPane何时改变感兴趣,请向该属性添加一个ChangeListener。清单 12-31 中的程序显示了如何创建和填充一个Accordion

// AccordionTest.java
// ... find in the book's download area.

Listing 12-31Using the Accordion Control

CSS 样式手风琴

一个Accordion的默认 CSS 样式类名是accordionAccordion不添加任何 CSS 属性。它包含一个first-titled-pane子结构,这是第一个TitledPane。以下样式设置所有TitledPane的标题栏的背景色和插图:

.accordion > .titled-pane > .title {
    -fx-background-color: burlywood;
        -fx-background-insets: 1;
}

下面的样式设置了Accordion的第一个TitledPane的标题栏的背景色:

.accordion > .first-titled-pane > .title {
    -fx-background-color: derive(red, 80%);
}

了解分页控件

Pagination用于显示一个大的单一内容,方法是将内容分成称为页面的小块,例如搜索结果。图 12-48 显示了一个Pagination控件。一个Pagination控件有一个页数,也就是其中的页数。如果页数未知,则页数可能不确定。每一页都有一个从零开始的索引。

img/336502_2_En_12_Fig48_HTML.png

图 12-48

控制

一个Pagination控件分为两个区域:

  • 内容区域

  • 航行区域

内容区域显示当前页面的内容。导航区域包含允许用户从一个页面导航到另一个页面的部分。您可以按顺序或随机地在页面之间导航。一个Pagination控制的部件如图 12-49 所示。

img/336502_2_En_12_Fig49_HTML.png

图 12-49

Pagination控件的组成部分

上一页和下一页箭头按钮允许用户分别导航到上一页和下一页。当您在第一页时,“上一页”按钮被禁用。当您在最后一页时,“下一页”按钮被禁用。页面指示器还允许您通过显示所有页码来导航到特定页面。默认情况下,页面指示器使用工具提示来显示页码,您可以选择使用 CSS 属性禁用页码。所选页面指示器显示当前页面。所选页面标签显示当前页面选择的详细信息。

Pagination类提供了几个构造器。它们以不同的方式配置控件。默认构造器创建一个控件,该控件具有不确定的页数,并以零作为选定页的索引,如下面的代码所示:

// Indeterminate page count and first page selected
Pagination pagination1 = new Pagination();

当页数不确定时,页面指示器标签显示x/...,其中 x 是当前页面索引加 1。

使用另一个构造器来指定页数,如下面的代码所示:

// 5 as the page count and first page selected
Pagination pagination2 = new Pagination(5);

您还可以使用另一个构造器来指定页数和选定的页索引,如下面的代码所示:

// 5 as the page count and second page selected (page index starts at 0)
Pagination pagination3 = new Pagination(5, 1);

Pagination类声明了一个INDETERMINATE常量,可用于指定不确定的页数,如以下代码所示:

// Indeterminate page count and second page selected
Pagination pagination4 = new Pagination(Pagination.INDETERMINATE, 1);

Pagination类包含以下属性:

  • currentPageIndex

  • maxPageIndicatorCount

  • pageCount

  • pageFactory

currentPageIndex是一个整数属性。它的值是要显示的页面的页面索引。默认值为零。您可以使用其中一个构造器或使用setCurrentPageIndex()方法来指定它的值。如果将其值设置为小于零,则第一页索引(为零)将被设置为其值。如果将其值设置为大于页数减 1,则其值将设置为页数减 1。如果您想知道新页面何时显示,可以在currentPageIndex属性中添加一个ChangeListener

maxPageIndicatorCount是一个整数属性。它设置要显示的页面指示器的最大数量。默认为十。如果其设置超出了页数范围,其值将保持不变。如果其值设置得太高,则减小该值,以便页面指示器的数量适合控件。您可以使用setMaxPageIndicatorCount()方法设置它的值。

pageCount是一个整数属性。它是Pagination控件中的页数。其值必须大于或等于 1。它默认为不确定。它的值可以在构造器中设置或者使用setPageCount()方法。

pageFactory是最重要的属性。它是一个Callback<Integer, Node>类型的对象属性。它用于生成页面。当需要显示页面时,控件调用传递页面索引的Callback对象的call()方法。call()方法返回一个作为页面内容的节点。下面的代码片段为一个Pagination控件创建并设置了一个页面工厂。页面工厂返回一个Label:

// Create a Pagination with an indeterminate page count
Pagination pagination = new Pagination();

// Create a page factory that returns a Label
Callback<Integer, Node> factory =
    pageIndex -> new Label("Content for page " + (pageIndex + 1));

// Set the page factory
pagination.setPageFactory(factory);

Tip

如果页面索引不存在,页面工厂的call()方法应该返回null。当call()方法返回null时,当前页面不变。

清单 12-32 中的程序展示了如何使用Pagination控件。它将页数设置为五。页面工厂返回一个带有显示页码的文本的Label。它将显示一个带有Pagination控件的窗口,类似于图 12-48 所示。

// PaginationTest.java
// ... find in the book's download area.

Listing 12-32Using the Pagination Control

页面指示器可以是数字按钮或项目符号按钮。默认情况下使用数字按钮。Pagination类包含一个名为STYLE_CLASS_BULLETString常量,如果你想使用项目按钮,它是控件的样式类。下面的代码片段创建了一个Pagination控件,并将其样式类设置为使用项目符号按钮作为页面指示器。图 12-50 显示了一个带有作为页面指示器的项目按钮的Pagination控件。

img/336502_2_En_12_Fig50_HTML.png

图 12-50

使用项目符号按钮作为页面指示器的Pagination控件

Pagination pagination = new Pagination(5);

// Use bullet page indicators
pagination.getStyleClass().add(Pagination.STYLE_CLASS_BULLET);

用 CSS 样式化分页

一个Pagination控件的默认 CSS 样式类名是paginationPagination增加了几个 CSS 属性:

  • -fx-max-page-indicator-count

  • -fx-arrows-visible

  • -fx-tooltip-visible

  • -fx-page-information-visible

  • -fx-page-information-alignment

属性指定要显示的页面指示器的最大数量。默认值为 10。属性指定上一页和下一页按钮是否可见。默认值为 true。-fx-tooltip-visible属性指定当鼠标悬停在页面指示器上时是否显示工具提示。默认值为 true。-fx-page-information-visible指定所选页面标签是否可见。默认值为 true。-fx-page-information-alignment指定所选页面标签相对于页面指示器的位置。可能的值有顶部、右侧、底部和左侧。默认值为 bottom,在页面指示器下方显示选定的页面指示器。

Pagination控件有两个StackPane类型的子结构:

  • page

  • pagination-control

page子结构代表内容区域。pagination-control子结构代表导航区域,它有以下子结构:

  • left-arrow-button

  • right-arrow-Button

  • bullet-button

  • number-button

  • page-information

left-arrow-buttonright-arrow-button子结构属于Button类型。它们分别代表“上一页”和“下一页”按钮。left-arrow-button子结构有一个left-arrow子结构,是一个StackPane,代表上一页按钮中的箭头。right-arrow-button子结构有一个right-arrow子结构,是一个StackPane,它代表下一页按钮中的箭头。bullet-buttonnumber-buttonToggleButton类型,代表页面指示器。page-information子结构是保存所选页面信息的Labelpagination-control子结构在一个叫做control-box的子结构中保存了上一页和下一页按钮以及页面指示器,这是一个HBox

下列样式使所选页面标签不可见,将页面背景设置为浅灰色,并在“上一页”、“下一页”和“页面指示器”按钮周围绘制边框。请参考modena.css文件,了解更多关于如何设计Pagination控件样式的细节。

.pagination  {
        -fx-page-information-visible: false;
}

.pagination > .page {
    -fx-background-color: lightgray;
}

.pagination  > .pagination-control > .control-box {
    -fx-padding: 2;
    -fx-border-style: dashed;
    -fx-border-width: 1;
    -fx-border-radius: 5;
    -fx-border-color: blue;
}

了解工具提示控件

工具提示是一个弹出控件,用于显示节点的附加信息。当鼠标指针悬停在节点上时,它会显示出来。当鼠标指针悬停在某个节点上时和显示该节点的工具提示时之间会有一小段延迟。工具提示在一小段时间后隐藏。当鼠标指针离开控件时,它也被隐藏。你不应该设计一个 GUI 应用程序,在那里用户依赖于看到控件的工具提示,因为如果鼠标指针从不停留在控件上,它们可能根本不会显示。图 12-51 显示了一个带有工具提示的窗口,显示保存数据文本。

img/336502_2_En_12_Fig51_HTML.jpg

图 12-51

显示工具提示的窗口

工具提示由继承自PopupControl类的Tooltip类的实例表示。工具提示可以包含文本和图形。您可以使用默认的构造器创建工具提示,该构造器没有文本和图形。您还可以使用其他构造器创建带有文本的工具提示,如下面的代码所示:

// Create a Tooltip with No text and no graphic
Tooltip tooltip1 = new Tooltip();

// Create a Tooltip with text
Tooltip tooltip2 = new Tooltip("Closes the window");

需要为使用Tooltip类的install()静态方法的节点安装一个工具提示。使用uninstall()静态方法卸载一个节点的工具提示:

Button saveBtn = new Button("Save");
Tooltip tooltip = new Tooltip("Saves the data");

// Install a tooltip
Tooltip.install(saveBtn, tooltip);
...
// Uninstall the tooltip
Tooltip.uninstall(saveBtn, tooltip);

工具提示经常用于 UI 控件。因此,为控件安装工具提示变得更加容易。Control类包含一个tooltip属性,它是一个Tooltip类型的对象属性。你可以使用Control类的setTooltip()方法为控件设置一个Tooltip。如果一个节点不是控件,例如一个Circle节点,您将需要使用install()方法来设置一个工具提示,如前面所示。以下代码片段显示了如何使用按钮的tooltip属性:

Button saveBtn = new Button("Save");

// Install a tooltip
saveBtn.setTooltip(new Tooltip("Saves the data"));
...
// Uninstall the tooltip
saveBtn.setTooltip(null);

Tip

工具提示可以在多个节点之间共享。工具提示使用一个Label控件来显示它的文本和图形。在内部,工具提示上设置的所有与内容相关的属性都委托给了Label控件。

Tooltip类包含几个属性:

  • text

  • graphic

  • contentDisplay

  • textAlignment

  • textOverrun

  • wrapText

  • graphicTextGap

  • font

  • activated

  • hideDelay

  • showDelay

  • showDuration

text属性是一个String属性,它是要在工具提示中显示的文本。graphic属性是一个Node类型的对象属性。它是工具提示的图标。contentDisplay属性是ContentDisplay枚举类型的对象属性。它指定图形相对于文本的位置。可能的值是ContentDisplay枚举中的常量之一:TOPRIGHTBOTTOMLEFTCENTERTEXT_ONLYGRAPHIC_ONLY。默认值为 LEFT,将图形放置在文本的左侧。

下面的代码片段使用一个图标作为工具提示,并将其放在文本上方。图标只是一个文本为 X 的Label。图 12-52 显示了刀头的外观。

img/336502_2_En_12_Fig52_HTML.jpg

图 12-52

使用图标并将其放在工具提示中文本的顶部

// Create and configure the Tooltip
Tooltip closeBtnTip = new Tooltip("Closes the window");
closeBtnTip.setStyle("-fx-background-color: yellow; -fx-text-fill: black;");

// Display the icon above the text
closeBtnTip.setContentDisplay(ContentDisplay.TOP);

Label closeTipIcon = new Label("X");
closeTipIcon.setStyle("-fx-text-fill: red;");
closeBtnTip.setGraphic(closeTipIcon);

// Create a Button and set its Tooltip
Button closeBtn = new Button("Close");
closeBtn.setTooltip(closeBtnTip);

textAlignment属性是TextAlignment枚举类型的对象属性。当文本跨越多行时,它指定文本对齐方式。可能的值是TextAlignment枚举中的常量之一:LEFTRIGHTCENTERJUSTIFY

textOverrun属性是OverrunStyle枚举类型的对象属性。它指定当工具提示中没有足够的空间来显示整个文本时要使用的行为。默认行为是使用省略号。

wrapText是一个boolean属性。它指定如果文本超出工具提示的宽度,是否应该换行。默认值为假。

graphicTextGap属性是一个double属性,它以像素为单位指定文本和图形之间的间距。默认值为四。font属性是一个Font类型的对象属性。它指定文本使用的默认字体。activated属性是只读的boolean属性。当工具提示被激活时,这是真的。否则就是假的。当鼠标移动到控件上时,工具提示被激活,并在激活后显示。

清单 12-33 中的程序显示了如何创建、配置和设置控件的工具提示。运行应用程序后,将鼠标指针放在 name 字段、Save 按钮和 Close 按钮上。过一会儿,他们的工具提示就会显示出来。“关闭”按钮的工具提示看起来与“保存”按钮不同。它使用一个图标和不同的背景和文本颜色。

// TooltipTest.java
// ... find in the book's download area.

Listing 12-33Using the Tooltip Control

用 CSS 样式化工具提示

一个Tooltip控件的默认 CSS 样式类名是tooltipTooltip增加了几个 CSS 属性:

  • -fx-text-alignment

  • -fx-text-overrun

  • -fx-wrap-text

  • -fx-graphic

  • -fx-content-display

  • -fx-graphic-text-gap

  • -fx-font

所有 CSS 属性都对应于Tooltip类中与内容相关的属性。有关所有这些属性的描述,请参考上一节。以下代码为Tooltip设置背景颜色、文本颜色和环绕文本属性:

.tooltip {
        -fx-background-color: yellow;
        -fx-text-fill: black;
        -fx-wrap-text: true;
}

在控件中提供滚动功能

JavaFX 提供了两个名为ScrollBarScrollPane的控件,为其他控件提供滚动功能。通常,这些控件不会单独使用。它们用于支持其他控件中的滚动。

理解滚动条控件

ScrollBar是一个基本控件,本身不提供滚动功能。它表示为一个水平或垂直的条,允许用户从一系列值中选择一个值。图 12-53 显示了水平和垂直滚动条。

img/336502_2_En_12_Fig53_HTML.png

图 12-53

水平和垂直滚动条及其部件

一个ScrollBar控件由四部分组成:

  • 增加数值的增量按钮

  • 减少按钮,用于减少数值

  • 显示当前值的拇指(或旋钮)

  • 拇指移动的轨迹

竖线ScrollBar中的增量和减量按钮分别位于底部和顶部。

ScrollBar类提供了创建水平滚动条的默认构造器。您可以使用setOrientation()方法将其方向设置为垂直:

// Create a horizontal scroll bar
ScrollBar hsb = new ScrollBar();

// Create a vertical scroll bar
ScrollBar vsb = new ScrollBar();
vsb.setOrientation(Orientation.VERTICAL);

minmax属性表示其值的范围。它的value属性是当前值。minmaxvalue属性的默认值分别为 0、100 和 0。如果您想知道value属性何时改变,您需要向它添加一个ChangeListener。以下代码将把value属性设置为 0、200 和 150:

ScrollBar hsb = new ScrollBar();
hsb.setMin(0);
hsb.setMax(200);
hsb.setValue(150);

滚动条的当前值可以通过三种不同的方式进行更改:

  • 以编程方式使用setValue()increment()decrement()方法

  • 通过用户在轨道上拖动拇指

  • 通过用户点击递增和递减按钮

blockIncrementunitIncrement属性分别指定当用户单击音轨和递增或递减按钮时调整当前值的量。通常,块增量设置为大于单位增量的值。

一个ScrollBar控件的默认 CSS 样式类名是scroll-barScrollBar支持两个 CSS 伪类:horizontalvertical。它的一些属性可以使用 CSS 来设置。

ScrollBar很少被开发者直接使用。它用于构建支持滚动的完整控件,例如ScrollPane控件。如果您需要为控件提供滚动功能,请使用ScrollPane,我将在下一节中讨论。

了解滚动条控件

一个ScrollPane提供了一个节点的可滚动视图。一个ScrollPane由一个水平ScrollBar、一个垂直ScrollBar和一个内容节点组成。ScrollPane提供滚动的节点是内容节点。如果您想要提供多个节点的可滚动视图,将它们添加到一个布局窗格,例如一个GridPane,然后将布局窗格作为内容节点添加到ScrollPaneScrollPane使用滚动策略指定何时显示特定的滚动条。内容可见的区域称为视窗。图 12-54 显示了一个以Label为内容节点的ScrollPane

img/336502_2_En_12_Fig54_HTML.jpg

图 12-54

以一个Label作为其内容节点的一个ScrollPane

Tip

一些常用的需要滚动功能的控件,例如一个TextArea,提供了一个内置的ScrollPane,它是这类控件的一部分。

您可以使用ScrollPane类的构造器创建一个空的ScrollPane或一个带有内容节点的ScrollPane,如下面的代码所示。您可以稍后使用setContent()方法设置内容节点。

Label poemLbl1 = ...
Label poemLbl2 = ...

// Create an empty ScrollPane
ScrollPane sPane1 = new ScrollPane();

// Set the content node for the ScrollPane
sPane1.setContent(poemLbl1);

// Create a ScrollPane with a content node
ScrollPane sPane2 = new ScrollPane(poemLbl2);

Tip

ScrollPane基于内容的布局边界为其内容提供滚动。如果内容使用效果或变换,例如缩放,您需要将内容包装在一个Group中,并将Group添加到ScrollPane中,以获得正确的滚动。

ScrollPane类包含几个属性,其中大多数通常不被开发人员使用:

  • content

  • pannable

  • fitToHeight

  • fitToWidth

  • hbarPolicy

  • vbarPolicy

  • hmin

  • hmax

  • hvalue

  • vmin

  • vmax

  • vvalue

  • prefViewportHeight

  • prefViewportWidth

  • viewportBounds

content属性是Node类型的对象属性,它指定了内容节点。您可以使用滚动条或平移来滚动内容。如果使用平移,您需要在按下左、右或两个按钮时拖动鼠标来滚动内容。默认情况下,ScrollPane是不可平移的,你需要使用滚动条来滚动内容。pannable属性是一个boolean属性,指定ScrollPane是否可平移。使用setPannable(true)方法使ScrollPane可平移。

fitToHeightfitToWidth属性分别指定是否调整内容节点的大小以匹配视口的高度和宽度。默认情况下,它们是假的。如果内容节点不可调整大小,则忽略这些属性。图 12-55 显示了与图 12-54 相同的ScrollPane,其fitToHeightfitToWidth属性设置为真。请注意,Label内容节点已经被调整大小以适合视窗。

img/336502_2_En_12_Fig55_HTML.jpg

图 12-55

fitToHeightfitToWidth属性设置为真的ScrollPane

hbarPolicyvbarPolicy属性是ScrollPane.ScrollBarPolicy枚举类型的对象属性。它们指定何时显示水平和垂直滚动条。可能的值有ALWAYSAS_NEEDEDNEVER。当策略设置为ALWAYS时,滚动条一直显示。当策略设置为AS_NEEDED时,滚动条会根据内容的大小在需要时显示。当策略设置为NEVER时,滚动条从不显示。

hminhmaxhvalue属性分别指定水平滚动条的最小值、最大值和值属性。vminvmaxvvalue属性分别指定垂直滚动条的最小值、最大值和值属性。通常,不设置这些属性。它们根据内容和用户滚动内容而变化。

prefViewportHeightprefViewportWidth分别是内容节点可用的视口的首选高度和宽度。

viewportBoundsBounds类型的对象属性。这是视口的实际边界。清单 12-34 中的程序展示了如何使用ScrollPane。它设置了一个有四行文本作为内容的Label。这也使得ScrollPane成为众矢之的。也就是说,你可以拖动鼠标点击它的按钮来滚动文本。

// ScrollPaneTest.java
// ... find in the book's download area.

Listing 12-34Using ScrollPane

一个ScrollPane控件的默认 CSS 样式类名是scroll-pane。样本样式请参考modena.css文件,ScrollPane支持的 CSS 属性和伪类的完整列表请参考在线 JavaFX CSS 参考指南

把事情分开

有时,您可能希望将逻辑上相关的控件水平或垂直并排放置。为了获得更好的外观,控件使用不同类型的分隔符进行分组。有时候,使用边框就足够了,但是有时候你会使用TitledPane控件。SeparatorSplitPane控件仅用于在视觉上区分两个控件或两组控件。

了解分离器控制

Separator是分隔两组控件的水平线或垂直线。通常,它们用在菜单或组合框中。图 12-56 显示由水平和垂直分隔符分隔的餐厅菜单项。

img/336502_2_En_12_Fig56_HTML.jpg

图 12-56

使用水平和垂直分隔符

默认的构造器创建一个水平的Separator。要创建一个垂直的Separator,可以在构造器中指定一个垂直方向或者使用setOrientation()方法,如下面的代码所示:

// Create a horizontal separator
Separator separator1 = new Separator();

// Change the orientation to vertical
separator1.setOrientation(Orientation.VERTICAL);

// Create a vertical separator
Separator separator2 = new Separator(Orientation.VERTICAL);

分隔符会自行调整大小以填充分配给它的空间。一个水平的Separator水平调整大小,一个垂直的Separator垂直调整大小。从内部来说,Separator就是Region。你可以用 CSS 改变它的颜色和厚度。

Separator类包含三个属性:

  • orientation

  • halignment

  • valignment

属性指定控件的方向。可能的值是Orientation枚举的两个常量之一:HORIZONTALVERTICALhalignment属性指定垂直分隔线宽度内分隔线的水平对齐方式。对于水平分隔符,该属性被忽略。可能的值是HPos枚举的常量之一:LEFTCENTERRIGHT。默认值是居中。v alignment属性指定水平分隔符高度内分隔线的垂直对齐方式。对于垂直分隔符,该属性被忽略。可能的值是VPos枚举的常量之一:BASELINETOPCENTERBOTTOM。默认值是居中。

带 CSS 的造型分隔符

一个Separator co控件的默认 CSS 样式类名是separatorSeparator包含 CSS 属性,对应其 Java 属性:

  • -fx-orientation

  • -fx-halignment

  • -fx-valignment

Separator支持分别应用于水平和垂直分隔符的horizontalvertical CSS 伪类。它包含一个line子结构,这是一个Region。您在分隔符中看到的线是通过指定line子结构的边界创建的。以下样式用于创建图 12-56 中的分隔符:

.separator > .line {
    -fx-border-style: solid;
    -fx-border-width: 1;
}

您可以使用图像作为分隔符。设置分隔符的适当宽度或高度,并使用图像作为背景图像。下面的代码假设separator.jpg图像文件与包含样式的 CSS 文件存在于同一个目录中。这些样式将水平分隔符的首选高度和垂直分隔符的首选宽度设置为 10px:

.separator {
        -fx-background-image: url("separator.jpg");
        -fx-background-repeat: repeat;
        -fx-background-position: center;
        -fx-background-size: cover;
}

.separator:horizontal {
        -fx-pref-height: 10;
}

.separator:vertical {
        -fx-pref-width: 10;
}

了解分割面板控件

SplitPane排列多个节点,用分隔线将它们水平或垂直分开。用户可以拖动分隔线,因此分隔线一侧的节点会扩展,另一侧的节点会收缩相同的量。通常,SplitPane中的每个节点都是一个包含一些控件的布局窗格。然而,你可以使用任何节点,例如一个Button。如果您使用过 Windows Explorer,那么您已经熟悉了使用SplitPane。在 Windows 资源管理器中,分隔线将树视图和列表视图分开。使用分隔线,您可以调整树视图的宽度,而列表视图的宽度会以相同的量向相反的方向调整。一个可调整大小的 HTML 框架集类似于一个SplitPane。图 12-57 显示了带有水平SplitPane的窗口。SplitPane包含两个VBox布局窗格;其中每个都包含一个Label和一个TextArea。图 12-57 显示分隔线向右拖动,左边的VBox比右边的宽度大。

img/336502_2_En_12_Fig57_HTML.png

图 12-57

一扇横窗SplitPane

您可以使用SplitPane类的默认构造器创建一个SplitPane:

SplitPane sp = new SplitPane();

SplitPane类的getItems()方法返回在SplitPane中存储节点列表的ObservableList<Node>。将所有节点添加到该列表中,如以下代码所示:

// Create panes
GridPane leftPane = new GridPane();
GridPane centerPane = new GridPane();
GridPane rightPane = new GridPane();

/* Populate the left, center, and right panes with controls here */

// Add panels to the a SplitPane
SplitPane sp = new SplitPane();
sp.getItems().addAll(leftPane, centerPane, rightPane);

默认情况下,SplitPane水平放置其节点。它的orientation属性可以用来指定方向:

// Place nodes vertically
sp.setOrientation(Orientation.VERTICAL);

分隔线可以在最左边和最右边或最上边和最下边之间移动,只要它不与任何其他分隔线重叠。分频器位置可以设置在 0 和 1 之间。位置 0 表示最顶端或最左边。位置 1 表示最底部或最右侧。默认情况下,分隔线放置在中间,其位置设定为 0.5。使用以下两种方法之一来设置分隔线的位置:

  • setDividerPositions(double... positions)

  • setDividerPosition(int dividerIndex, double position)

setDividerPositions()方法获取多个分隔线的位置。您必须提供从开始到您想要设置位置的所有分隔线的位置。

如果要设置特定分隔线的位置,使用setDividerPosition()方法。第一个分频器的索引为 0。忽略为超出范围的索引传入的位置。

方法返回所有分隔线的位置。它返回一个double数组。分隔符的索引与数组元素的索引相匹配。

默认情况下,SplitPane在调整大小时会调整其节点的大小。您可以使用setResizableWithParent()静态方法通过SplitPane阻止特定节点调整大小:

// Make node1 non-resizable
SplitPane.setResizableWithParent(node1, false);

清单 12-35 中的程序展示了如何使用SplitPane。显示如图 12-57 所示的窗口。运行程序并使用鼠标向左或向右拖动分隔线,以调整左右节点的间距。

// SplitPaneTest.java
// ... find in the book's download area.

Listing 12-35Using SplitPane Controls

使用 CSS 样式化分割窗格

一个SplitPane co控件的默认 CSS 样式类名是split-paneSplitPane包含-fx-orientation CSS 属性,决定其方向。可能的值是horizontalvertical

SplitPane支持分别应用于水平和垂直SplitPaneshorizontalvertical CSS 伪类。分频器是SplitPanesplit-pane-divider子结构,?? 是StackPane。下面的代码为分隔线设置了蓝色背景,为水平方向的分隔线设置了 5px 的首选宽度SplitPane,为垂直方向的分隔线设置了 5px 的首选高度SplitPane:

.split-pane > .split-pane-divider {
    -fx-background-color: blue;
}

.split-pane:horizontal > .split-pane-divider {
    -fx-pref-width: 5;
}

.split-pane:vertical > .split-pane-divider {
    -fx-pref-height: 5;
}

split-pane-divider子结构包含一个抓取器子结构,它是一个StackPane。它的 CSS 样式类名是横向SplitPanehorizontal-grabber和纵向SplitPanevertical-grabber。抓取器显示在分隔器的中间。

了解滑块控件

Slider让用户通过沿轨迹滑动拇指(或旋钮)从数值范围中选择一个数值。滑块可以是水平的或垂直的。图 12-58 所示为水平滑块。

img/336502_2_En_12_Fig58_HTML.png

图 12-58

水平Slider控件及其零件

滑块具有确定有效可选值范围的最小值和最大值。滑块的拇指表示其当前值。您可以沿轨道滑动滑块来更改当前值。主要和次要刻度线显示值在轨道上的位置。您也可以显示刻度标签。也支持自定义标签。

下面的代码使用默认构造器创建一个Slider控件,该构造器将 0、100 和 0 分别设置为最小值、最大值和当前值。默认方向是水平的。

// Create a horizontal slider
Slider s1 = new Slider();

使用另一个构造器来指定最小值、最大值和当前值:

// Create a horizontal slider with the specified min, max, and value
double min = 0.0;
double max = 200.0;
double value = 50.0;
Slider s2 = new Slider(min, max, value);

一个Slider控件包含几个属性。我将按类别讨论它们。

orientation属性指定滑块的方向:

// Create a vertical slider
Slider vs = new Slider();
vs.setOrientation(Orientation.VERTICAL);

以下属性与当前值和值的范围相关:

  • min

  • max

  • value

  • valueChanging

  • snapToTicks

minmaxvalue属性是double属性,它们分别代表滑块的最小值、最大值和当前值。滑块的当前值可以通过在轨道上拖动拇指或使用setValue()方法来改变。以下代码片段创建了一个滑块,并将其minmaxvalue属性分别设置为 0、10 和 3:

Slider scoreSlider = new Slider();
scoreSlider.setMin(0.0);
scoreSlider.setMax(10.0);
scoreSlider.setValue(3.0);

通常,您希望在滑块的value属性改变时执行一个动作。您需要向value属性添加一个ChangeListener。以下语句使用 lambda 表达式将一个ChangeListener添加到scoreSlider控件,并在 value 属性更改时打印旧值和新值:

scoreSlider.valueProperty().addListener(
         (ObservableValue<? extends Number> prop, Number oldVal,
             Number newVal) -> {
    System.out.println("Changed from " + oldVal + " to " + newVal);
});

valueChanging属性是一个boolean属性。当用户按下拇指时,它被设置为 true,当松开拇指时,它被设置为 false。随着用户拖动拇指,该值不断变化,并且valueChanging属性为真。如果您只想在值更改时执行一次操作,此属性有助于避免重复操作。

snapToTicks属性是一个boolean属性,默认为 false。它指定滑块的value属性是否总是与刻度线对齐。如果设置为 false,该值可以是minmax范围内的任何值。

ChangeListener中使用valueChanging属性时要小心。对于用户看到的一个变化,侦听器可能会被调用多次。期望当valueChanging属性从 true 变为 false 时ChangeListener会得到通知,您将动作的主要逻辑包装在一个if语句中:

if (scoreSlider.isValueChanging()) {
        // Do not perform any action as the value changes
} else {
        // Perform the action as the value has been changed
}

snapToTicks属性设置为 true 时,逻辑工作正常。只有当snapToTicks属性设置为真时,当valueChanging属性从真变为假时,才会通知value属性的ChangeListener。因此,除非已经将snapToTicks属性也设置为 true,否则不要编写前面的逻辑。

Slider类的以下属性指定了刻度间距:

  • majorTickUnit

  • minorTickCount

  • blockIncrement

majorTickUnit属性是一个double属性。它指定两个主要刻度之间的距离单位。假设min属性被设置为 0,而majorTickUnit被设置为 10。滑块将在 0、10、20、30 等位置有主要刻度。此属性的值超出范围将禁用主刻度。该属性的默认值为 25。

minorTickCount属性是一个整数属性。它指定两个主要刻度之间的次要刻度数。属性的默认值为 3。

您可以使用按键来更改缩略图的位置,例如,在水平滑块中使用左右箭头键,在垂直滑块中使用上下箭头键。blockIncrement属性是一个double属性。它指定当拇指使用按键操作时滑块当前值的调整量。该属性的默认值为 10。

下列属性指定是否显示刻度线和刻度标签;默认情况下,它们被设置为 false:

  • showTickMarks

  • showTickLabels

labelFormatter属性是StringConverter<Double>类型的对象属性。默认情况下,它是null,滑块使用默认的StringConverter,显示主要刻度的数值。主要刻度的值被传递给toString()方法,该方法应该为该值返回一个自定义标签。以下代码片段创建了一个带有自定义主要刻度标签的滑块,如图 12-59 所示:

img/336502_2_En_12_Fig59_HTML.jpg

图 12-59

带有自定义主要刻度标签的滑块

Slider scoreSlider = new Slider();
scoreSlider.setShowTickLabels(true);
scoreSlider.setShowTickMarks(true);
scoreSlider.setMajorTickUnit(10);
scoreSlider.setMinorTickCount(3);
scoreSlider.setBlockIncrement(20);
scoreSlider.setSnapToTicks(true);

// Set a custom major tick formatter
scoreSlider.setLabelFormatter(new StringConverter<Double>() {
        @Override
        public String toString(Double value) {
                String label = "";
                if (value == 40) {
                        label = "F";
                } else if (value == 70) {
                        label = "C";
                } else if (value == 80) {
                        label = "B";
                } else if (value == 90) {
                        label = "A";
                }

                return label;
        }

        @Override
        public Double fromString(String string) {
                return null; // Not used
        }
});

清单 12-36 中的程序展示了如何使用Slider控件。它向一个窗口添加了一个Rectangle、一个Label和三个Slider控件。它给Slider增加了一个ChangeListenerSlider代表一种颜色的红色、绿色和蓝色成分。当您更改滑块的值时,会计算新颜色并将其设置为矩形的填充颜色。

// SliderTest.java
// ... find in the book's download area.

Listing 12-36Using the Slider Control

CSS 样式化滑块

一个Slider co控件的默认 CSS 样式类名是sliderSlider包含以下 CSS 属性;它们中的每一个都对应于其在Slider类中的 Java 属性:

  • -fx-orientation

  • -fx-show-tick-labels

  • -fx-show-tick-marks

  • -fx-major-tick-unit

  • -fx-minor-tick-count

  • -fx-snap-to-ticks

  • -fx-block-increment

Slider支持分别应用于水平和垂直滑块的horizontalvertical CSS 伪类。一个Slider控件包含三个可以样式化的子结构:

  • axis

  • track

  • thumb

axis底座是一个NumberAxis。它显示刻度线和刻度标签。以下代码将刻度标签颜色设置为蓝色,主刻度长度设置为 15px,次刻度长度设置为 5px,主刻度颜色设置为红色,次刻度颜色设置为绿色:

.slider > .axis {
    -fx-tick-label-fill: blue;
    -fx-tick-length: 15px;
    -fx-minor-tick-length: 5px
}

.slider > .axis > .axis-tick-mark {
    -fx-stroke: red;
}

.slider > .axis > .axis-minor-tick-mark {
    -fx-stroke: green;
}

track底座是一个StackPane。下面的代码将track的背景色改为红色:

.slider > .track {
    -fx-background-color: red;
}

thumb底座是一个StackPane。拇指看起来是圆形的,因为它有一个背景半径。如果删除背景半径,它将看起来是矩形的,如下面的代码所示:

.slider .thumb {
    -fx-background-radius: 0;
}

您可以通过将thumb子结构的背景设置为如下所示的图像来制作类似于拇指的图像(假设thumb.jpg图像文件与包含该样式的 CSS 文件存在于同一目录中):

.slider .thumb {
        -fx-background-image: url("thumb.jpg");
}

您可以使用-fx-shape CSS 属性赋予缩略图任何形状。下面的代码给出了一个三角形的缩略图。对于水平滑块,三角形是倒置的,对于垂直滑块,三角形指向右边。图 12-60 显示了带有拇指的水平滑块。

img/336502_2_En_12_Fig60_HTML.jpg

图 12-60

带有倒三角形滑块的滑块

/* An inverted triangle */
.slider > .thumb {
        -fx-shape: "M0, 0L10, 0L5, 10 Z";
}

/* A triangle pointing to the right, only if orientation is vertical */
.slider:vertical > .thumb {
        -fx-shape: "M0, 0L10, 5L0, 10 Z";
}

下面的代码给出了一个放置在矩形旁边的三角形。对于水平滑块,三角形是倒置的,对于垂直滑块,三角形指向右边。图 12-61 显示了带有拇指的水平滑块。

img/336502_2_En_12_Fig61_HTML.jpg

图 12-61

矩形下方带有倒三角形拇指的滑块

/* An inverted triangle below a rectangle*/
.slider > .thumb {
        -fx-shape: "M0, 0L10, 0L10, 5L5, 10L0, 5 Z";
}

/* A triangle pointing to the right by the right side of a rectangle */
.slider:vertical > .thumb {
        -fx-shape: "M0, 0L5, 0L10, 5L5, 10L0, 10 Z";
}

理解菜单

菜单用于以紧凑的形式向用户提供可操作项目的列表。您还可以使用一组按钮提供相同的项目列表,其中每个按钮代表一个可操作的项目。使用哪一个是你的偏好问题:一个菜单或一组按钮。

使用菜单有一个明显的优点。与一组按钮相比,通过将一组项目折叠(或嵌套)在另一个项目下,它占用的屏幕空间要少得多。例如,如果您使用了文件编辑器,诸如“新建”、“打开”、“保存”和“打印”等菜单项会嵌套在顶级文件菜单下。用户需要单击文件菜单来查看其下可用的项目列表。通常,在一组按钮的情况下,所有项目对用户都是可见的,并且用户很容易知道哪些动作是可用的。因此,当你决定使用菜单或按钮时,在空间和可用性之间没有什么权衡。通常,菜单栏显示在窗口的顶部。

Tip

还有另外一种菜单,叫做上下文菜单或者弹出菜单,按需显示。我将在下一节讨论上下文菜单。

菜单由几部分组成。图 12-62 显示了另存为子菜单展开时的菜单及其组成部分。菜单栏是包含菜单的菜单的最顶端部分。菜单栏始终可见。文件、编辑、选项和帮助是如图 12-62 所示的菜单项。菜单包含菜单项和子菜单。在图 12-62 中,文件菜单包含四个菜单项:新建、打开、保存、退出;它包含两个分隔符菜单项和一个另存为子菜单。“另存为”子菜单包含两个菜单项:文本和 PDF。菜单项是可操作的项目。分隔符菜单项有一条水平线,将菜单中的一组相关菜单项与另一组菜单项分隔开来。通常,菜单代表一类项目。

img/336502_2_En_12_Fig62_HTML.png

图 12-62

带有菜单栏、菜单、子菜单、分隔符和菜单项的菜单

使用菜单是一个多步骤的过程。以下部分详细描述了这些步骤。以下是步骤摘要:

  1. 创建一个菜单栏并将其添加到一个容器中。

  2. 创建菜单并将其添加到菜单栏。

  3. 创建菜单项并将其添加到菜单中。

  4. ActionEvent处理程序添加到菜单项中,以便在菜单项被单击时执行操作。

使用菜单栏

菜单栏是作为菜单容器的水平栏。MenuBar类的一个实例代表一个菜单栏。您可以使用默认的构造器创建一个MenuBar:

MenuBar menuBar = new MenuBar();

MenuBar是控件。通常,它被添加到窗口的顶部。如果你使用一个BorderPane作为窗口中场景的根,顶部区域通常是一个MenuBar的位置:

// Add the MenuBar to the top region
BorderPane root = new BorderPane();
root.setBottom(menuBar);

MenuBar类包含一个useSystemMenuBar属性,它属于boolean类型。默认情况下,它被设置为 false。当设置为 true 时,如果平台支持,它将使用系统菜单栏。例如,Mac 支持系统菜单栏。如果在 Mac 上将该属性设置为 true,MenuBar将使用系统菜单栏显示其项目:

// Let the MenuBar use system menu bar
menuBar.setUseSystemMenuBar(true);

一个MenuBar本身不占用任何空间,除非你给它添加菜单。它的大小是根据它所包含的菜单的细节来计算的。一个MenuBar将它所有的菜单存储在一个MenuObservableList中,其引用由它的getMenus()方法返回:

// Add some menus to the MenuBar
Menu fileMenu = new Menu("File");
Menu editMenu = new Menu("Edit");
menuBar.getMenus().addAll(fileMenu, editMenu);

使用菜单

菜单包含可操作项目的列表,可根据需要显示,例如,通过单击它。当用户选择一个项目或将鼠标指针移出列表时,菜单项列表隐藏。菜单通常作为子菜单添加到菜单栏或其他菜单中。

Menu类的一个实例代表一个菜单。菜单显示文本和图形。使用默认构造器创建一个空菜单,然后设置文本和图形:

// Create a Menu with an empty string text and no graphic
Menu aMenu = new Menu();

// Set the text and graphic to the Menu
aMenu.setText("Text");
aMenu.setGraphic(new ImageView(new Image("image.jpg")));

您可以使用其他构造器创建包含文本或文本和图形的菜单:

// Create a File Menu
Menu fileMenu1 = new Menu("File");

// Create a File Menu
Menu fileMenu2 = new Menu("File", new ImageView(new Image("file.jpg")));

Menu类继承自MenuItem类,后者继承自Object类。Menu不是节点,因此不能直接添加到场景图中。你需要把它添加到一个MenuBar。使用getMenus()方法获取MenuBarObservableList<Menu>,并将Menu类的实例添加到列表中。下面的代码片段向一个MenuBar添加了四个Menu实例:

Menu fileMenu = new Menu("File");
Menu editMenu = new Menu("Edit");
Menu optionsMenu = new Menu("Options");
Menu helpMenu = new Menu("Help");

// Add menus to a menu bar
MenuBar menuBar = new MenuBar();
menuBar.getMenus().addAll(fileMenu, editMenu, optionsMenu, helpMenu);

单击菜单时,通常会显示其菜单项列表,但不会执行任何操作。Menu类包含以下属性,当它的选项列表分别显示、显示、隐藏和隐藏时,可以设置这些属性进行处理:

  • onShowing

  • onShown

  • onHiding

  • onHidden

  • showing

在显示菜单的菜单项之前,调用onShowing事件处理程序。显示菜单项后,调用onShown事件处理程序。onHidingonHidden事件处理程序分别对应于onShowingonShown事件处理程序。

通常,您会添加一个onShowing事件处理程序,它会根据某些标准启用或禁用菜单项。例如,假设您有一个带有剪切、复制和粘贴菜单项的编辑菜单。在onShowing事件处理程序中,您可以根据焦点是否在文本输入控件中、控件是否被启用或者控件是否有选择来启用或禁用这些菜单项:

editMenu.setOnAction(e -> {/* Enable/disable menu items here */});

Tip

用户在使用 GUI 应用程序时不喜欢惊喜。为了获得更好的用户体验,您应该禁用菜单项,而不是在它们不适用时使它们不可见。使它们不可见会改变其他项目的位置,用户必须重新定位它们。

showing属性是只读的boolean属性。当菜单中的项目显示时,它被设置为 true。当它们被隐藏时,它被设置为 false。

清单 12-37 中的程序将所有这些放在一起。它创建了四个菜单,一个菜单栏,将菜单添加到菜单栏,并将菜单栏添加到一个BorderPane的顶部区域。图 12-63 显示了窗口中的菜单栏。但是你还没有看到任何令人兴奋的菜单!您需要在菜单中添加菜单项来体验一些刺激。

img/336502_2_En_12_Fig63_HTML.jpg

图 12-63

有四个菜单的菜单栏

// MenuTest.java
package com.jdojo.control;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

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

        @Override
        public void start(Stage stage) {
                // Create some menus
                Menu fileMenu = new Menu("File");
                Menu editMenu = new Menu("Edit");
                Menu optionsMenu = new Menu("Options");
                Menu helpMenu = new Menu("Help");

                // Add menus to a menu bar
                MenuBar menuBar = new MenuBar();
                menuBar.getMenus().addAll(
                         fileMenu, editMenu, optionsMenu, helpMenu);

                BorderPane root = new BorderPane();
                root.setTop(menuBar);
                root.setStyle("""
                         -fx-padding: 10;
                   -fx-border-style: solid inside;
                   -fx-border-width: 2;
                   -fx-border-insets: 5;
                   -fx-border-radius: 5;
                   -fx-border-color: blue;""");

                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.setTitle("Using Menus");
                stage.show();
        }
}

Listing 12-37Creating a Menu Bar and Adding Menus to It

使用菜单项

菜单项是菜单中可操作的项目。与菜单项相关联的动作由鼠标或按键来执行。菜单项可以使用 CSS 样式。

MenuItem类的一个实例代表一个菜单项。MenuItem类不是一个节点。它继承自Object类,因此不能直接添加到场景图中。您需要将它添加到菜单中。

您可以将几种类型的菜单项添加到菜单中。图 12-64 显示了代表特定类型菜单项的MenuItem类及其子类的类图。

img/336502_2_En_12_Fig64_HTML.jpg

图 12-64

MenuItem类及其子类的类图

您可以使用以下类型的菜单项:

  • 一个可操作选项的MenuItem

  • A RadioMenuItem为一组互斥选项

  • 一个CheckMenuItem用于切换选项

  • 一个Menu,当用作菜单项时,作为一个保存菜单项列表的子菜单

  • 一个CustomMenuItem用于将任意节点用作菜单项

  • 一个SeparatorMenuItem,它是一个CustomMenuItem,用于将分隔符显示为菜单项

我将在接下来的小节中详细讨论所有的菜单项类型。

使用菜单项

一个MenuItem代表一个可操作的选项。当它被点击时,注册的ActionEvent处理程序被调用。下面的代码片段创建了一个退出MenuItem,并添加了一个退出应用程序的ActionEvent处理程序:

MenuItem exitItem = new MenuItem("Exit");
exitItem.setOnAction(e -> Platform.exit());

一个MenuItem被添加到菜单中。菜单将它的MenuItem的引用存储在一个ObservableList<MenuItem>中,其引用可以使用getItems()方法获得:

Menu fileMenu = new Menu("File");
fileMenu.getItems().add(exitItem);

MenuItem类包含以下适用于所有类型菜单项的属性:

  • text

  • graphic

  • disable

  • visible

  • accelerator

  • mnemonicParsing

  • onAction

  • onMenuValidation

  • parentMenu

  • parentPopup

  • style

  • id

textgraphic属性分别是菜单项的文本和图形,属于StringNode类型。disablevisible属性是boolean属性。它们指定菜单项是否被禁用和可见。accelerator属性是KeyCombination类型的对象属性,它指定了一个组合键,该组合键可用于在一次击键中执行与菜单项相关联的动作。下面的代码片段创建了一个Rectangle菜单项,并将其快捷键设置为 Alt + R,菜单项的快捷键显示在它的旁边,如图 12-65 所示,因此用户可以通过查看菜单项来了解它。用户可以通过按 Alt + R 直接激活矩形菜单项。

img/336502_2_En_12_Fig65_HTML.jpg

图 12-65

带有快捷键 Alt + R 的菜单项

MenuItem rectItem = new MenuItem("Rectangle");
KeyCombination kr = new KeyCodeCombination(KeyCode.R, KeyCombination.ALT_DOWN);
rectItem.setAccelerator(kr);

mnemonicParsing属性是一个boolean属性。它启用或禁用文本分析来检测助记符。默认情况下,对于菜单项,它设置为 true。如果设置为 true,则分析菜单项文本中的下划线字符。第一个下划线后面的字符被添加为菜单项的助记符。在 Windows 上按 Alt 键会突出显示所有菜单项的助记键。通常,助记符以下划线字体显示。按助记符键激活菜单项。

// Create a menu item with x as its mnemonic character
MenuItem exitItem = new MenuItem("E_xit");

onAction属性是一个ActionEvent处理程序,当菜单项被激活时调用,例如,通过用鼠标单击它或按下它的快捷键:

// Close the application when the Exit menu item is activated
exitItem.setOnAction(e -> Platform.exit());

onMenuValidation属性是一个事件处理程序,当使用其加速器访问MenuItem时,或者当调用其菜单(父菜单)的onShowing事件处理程序时,会调用该事件处理程序。对于菜单,当显示菜单项时,将调用此处理程序。

parentMenu属性是Menu类型的只读对象属性。它是包含菜单项的Menu的引用。使用这个属性和由Menu类的getItems()方法返回的项目列表,您可以从上到下导航菜单树,反之亦然。

parentPopup属性是ContextMenu类型的只读对象属性。它是菜单项出现的ContextMenu的引用。正常菜单中出现的菜单项为null

包含 style 和 ID 属性是为了支持使用 CSS 的样式。它们代表 CSS 样式和 ID。

使用单选按钮

一个RadioMenuItem代表一个互斥选项。通常,您将多个RadioMenuItem添加到一个ToggleGroup,因此只有一个项目被选中。RadioMenuItem选中时显示复选标记。下面的代码片段创建了三个RadioMenuItem实例,并将它们添加到一个ToggleGroup。最后,它们都被添加到一个文件Menu中。通常情况下,组中的RadioMenuItem被默认选中。图 12-66 显示了一组RadioMenuItem s:选择矩形时一次,选择圆形时一次。

img/336502_2_En_12_Fig66_HTML.jpg

图 12-66

RadioMenuItems在行动

// Create three RadioMenuItems
RadioMenuItem rectItem = new RadioMenuItem("Rectangle");
RadioMenuItem circleItem = new RadioMenuItem("Circle");
RadioMenuItem ellipseItem = new RadioMenuItem("Ellipse");

// Select the Rantangle option by default
rectItem.setSelected(true);

// Add them to a ToggleGroup to make them mutually exclusive
ToggleGroup shapeGroup = new ToggleGroup();
shapeGroup.getToggles().addAll(rectItem, circleItem, ellipseItem);

// Add RadioMenuItems to a File Menu
Menu fileMenu = new Menu("File");
fileMenu.getItems().addAll(rectItem, circleItem, ellipseItem);

如果您想在选择RadioMenuItem时执行一个动作,那么在它上面添加一个ActionEvent处理程序。下面的代码片段为每个RadioMenuItem添加了一个ActionEvent处理程序,它调用一个draw()方法:

rectItem.setOnAction(e -> draw());
circleItem.setOnAction(e -> draw());
ellipseItem.setOnAction(e -> draw());

使用检查菜单项

使用CheckMenuItem代表一个布尔菜单项,可以在选中和未选中状态之间切换。假设您有一个绘制形状的应用程序。可以有一个 Draw Stroke 菜单项作为CheckMenuItem。选中它时,将为该形状绘制一个笔划。否则,该形状将没有描边,如下面的代码所示。使用一个ActionEvent处理器,当CheckMenuItem的状态被切换时得到通知。

CheckMenuItem strokeItem = new CheckMenuItem("Draw Stroke");
strokeItem.setOnAction( e -> drawStroke());

选择CheckMenuItem时,旁边会显示一个复选标记。

使用子菜单项

注意,Menu类是从MenuItem类继承而来的。这使得使用Menu代替MenuItem成为可能。使用Menu作为菜单项创建子菜单。当鼠标悬停在子菜单上时,会显示其选项列表。

以下代码片段创建了一个MenuBar,添加了一个文件菜单,向文件菜单添加了新的和打开的MenuItem和一个另存为子菜单,并向另存为子菜单添加了文本和 PDF 菜单项。产生如图 12-67 所示的菜单。

img/336502_2_En_12_Fig67_HTML.jpg

图 12-67

用作子菜单的菜单

MenuBar menuBar = new MenuBar();
Menu fileMenu = new Menu("File");
menuBar.getMenus().addAll(fileMenu);

MenuItem newItem = new MenuItem("New");
MenuItem openItem = new MenuItem("Open");
Menu saveAsSubMenu = new Menu("Save As");

// Add menu items to the File menu
fileMenu.getItems().addAll(newItem, openItem, saveAsSubMenu);

MenuItem textItem = new MenuItem("Text");
MenuItem pdfItem = new MenuItem("PDF");
saveAsSubMenu.getItems().addAll(textItem, pdfItem);

通常,不需要为子菜单添加ActionEvent处理程序。相反,您可以为onShowing属性设置一个事件处理程序,在显示子菜单的项目列表之前调用该事件处理程序。事件处理程序用于启用或禁用菜单项。

使用自定义菜单项

是一个简单而强大的菜单项类型。它为设计菜单项的各种创意打开了大门。它允许您使用任何节点。例如,您可以使用一个SliderTextFieldHBox作为菜单项。CustomMenuItem类包含两个属性:

  • content

  • hideOnClick

content属性是Node类型的对象属性。它的值是要用作菜单项的节点。

单击菜单项时,所有可见的菜单都将隐藏,只有菜单栏中的顶级菜单保持可见。当您使用包含控件的自定义菜单项时,您不希望在用户单击它时隐藏菜单,因为用户需要与菜单项交互,例如,输入或选择一些数据。hideOnClick属性是一个boolean属性,允许您控制这种行为。默认情况下,它设置为 true,这意味着单击自定义菜单会隐藏所有显示的菜单。

CustomMenuItem类提供了几个构造器。默认构造器创建一个自定义菜单项,将content属性设置为null并将hideOnClick属性设置为 true,如以下代码所示:

// Create a Slider control
Slider slider = new Slider(1, 10, 1);

// Create a custom menu item and set its content and hideOnClick properties
CustomMenuItem cmi1 = new CustomMenuItem();
cmi1.setContent(slider);
cmi1.setHideOnClick(false);

// Create a custom menu item with a Slider content and
// set the hideOnClick property to false
CustomMenuItem cmi2 = new CustomMenuItem(slider);
cmi1.setHideOnClick(false);

// Create a custom menu item with a Slider content and false hideOnClick
CustomMenuItem cmi2 = new CustomMenuItem(slider, false);

下面的代码片段产生如图 12-68 所示的菜单。菜单项之一是一个CustomMenuItem,它使用一个slider作为它的内容:

img/336502_2_En_12_Fig68_HTML.jpg

图 12-68

作为自定义菜单项的滑块

CheckMenuItem strokeItem = new CheckMenuItem("Draw Stroke");
strokeItem.setSelected(true);

Slider strokeWidthSlider = new Slider(1, 10, 1);
strokeWidthSlider.setShowTickLabels(true);
strokeWidthSlider.setShowTickMarks(true);
strokeWidthSlider.setMajorTickUnit(2);
CustomMenuItem strokeWidthItem = new CustomMenuItem(strokeWidthSlider, false);

Menu optionsMenu = new Menu("Options");
optionsMenu.getItems().addAll(strokeItem, strokeWidthItem);

MenuBar menuBar = new MenuBar();
menuBar.getMenus().add(optionsMenu);

使用分隔符菜单项

关于SeparatorMenuItem没有什么特别要讨论的。它继承自CustomMenuItem。它使用一个水平的Separator控件作为它的content,并将hideOnClick设置为假。它用于分隔属于不同组的菜单项,如下面的代码所示。它提供了一个默认的构造器:

// Create a separator menu item
SeparatorMenuItem smi = SeparatorMenuItem();

将菜单的所有部分放在一起

理解菜单的各个部分很容易。然而,在代码中使用它们是很棘手的,因为您必须单独创建所有部分,向它们添加侦听器,然后组装它们。

清单 12-38 中的程序使用菜单创建一个形状绘制应用程序。它使用所有类型的菜单项。程序显示一个窗口,窗口的根是一个BorderPane。顶部区域包含一个菜单,中间区域包含一个在其上绘制形状的画布。

运行应用程序并使用文件菜单绘制不同类型的形状;单击“清除”菜单项将清除画布。单击退出菜单项关闭应用程序。

使用选项菜单绘制或不绘制笔划并设置笔划宽度。请注意,滑块被用作选项菜单下的自定义菜单项。当您调整滑块值时,所绘制形状的描边宽度也会相应调整。绘制笔划菜单项是一个CheckMenuItem。取消选择它时,滑块菜单项被停用,并且形状不使用笔画。

// MenuItemTest.java
// ... find in the book's download area.

Listing 12-38Using Menus in a Shape Drawing Application

使用 CSS 设计菜单样式

使用菜单涉及几个组件。表 12-6 列出了菜单相关组件的默认 CSS 样式类名。

表 12-6

菜单相关组件的 CSS 默认样式类名

|

菜单组件

|

样式类名

| | --- | --- | | MenuBar | menu-bar | | Menu | menu | | MenuItem | menu-item | | RadioMenuItem | radio-menu-item | | CheckMenuItem | check-menu-item | | CustomMenuItem | custom-menu-item | | SeparatorMenuItem | separator-menu-item |

MenuBar支持一个-fx-use-system-menu-bar属性,默认设置为 false。它指示菜单栏是否使用系统菜单。它包含一个保存菜单栏菜单的menu子结构。Menu支持显示 CSS 伪类,在菜单显示时应用。RadioMenuItemCheckMenuItem支持一个selected CSS 伪类,当菜单项被选中时应用。

您可以设置菜单的几个组件的样式。样本样式请参考modena.css文件。

了解上下文菜单控件

ContextMenu是一个弹出控件,根据请求显示菜单项列表。它也被称为上下文弹出菜单。默认情况下,它是隐藏的。用户必须提出请求,通常是通过右击鼠标按钮来显示它。一旦做出选择,它将被隐藏。用户可以通过按 Esc 键或在上下文菜单边界外单击来关闭上下文菜单。

上下文菜单存在可用性问题。用户很难知道它的存在。通常,非技术用户不习惯点击鼠标右键并进行选择。对于这些用户,您可以使用工具栏或按钮来呈现相同的选项。有时,屏幕上会显示一条文本消息,说明用户需要右键单击才能查看或显示上下文菜单。

ContextMenu类的一个对象代表一个上下文菜单。它将菜单项的引用存储在一个ObservableList<MenuItem>中。getItems()方法返回可观察列表的引用。

您将在下面的示例中使用以下三个菜单项。注意,上下文菜单中的菜单项可以是MenuItem类或其子类的对象。有关菜单项类型的完整列表,请参考“理解菜单”一节。

MenuItem rectItem = new MenuItem("Rectangle");
MenuItem circleItem = new MenuItem("Circle");
MenuItem ellipseItem = new MenuItem("Ellipse");

ContextMenu类的默认构造器创建一个空菜单。您需要稍后添加菜单项:

ContextMenu ctxMenu = new ContextMenu();
ctxMenu.getItems().addAll(rectItem, circleItem, ellipseItem);

您可以使用另一个构造器创建包含菜单项初始列表的上下文菜单:

ContextMenu ctxMenu = new ContextMenu(rectItem, circleItem, ellipseItem);

通常,为控件提供上下文菜单以访问其常用功能,例如,文本输入控件的剪切、复制和粘贴功能。一些控件有默认的上下文菜单。control 类使显示上下文菜单变得容易。它有一个contextMenu属性。您需要将此属性设置为控件的上下文菜单引用。以下代码片段设置了一个TextField控件的上下文菜单:

ContextMenu ctxMenu = ...
TextField nameFld = new TextField();
nameFld.setContextMenu(ctxMenu);

当您右键单击TextField时,将显示您的上下文菜单,而不是默认菜单。

Tip

激活空的上下文菜单不会显示任何内容。如果您想禁用控件的默认上下文菜单,请将其contextMenu属性设置为空的ContextMenu

不是控件的节点没有contextMenu属性。您需要使用ContextMenu类的show()方法来显示这些节点的上下文菜单。show()方法让您可以完全控制上下文菜单的显示位置。如果您想微调上下文菜单的位置,也可以将它用于控件。show()方法被重载:

void show(Node anchor, double screenX, double screenY)
void show(Node anchor, Side side, double dx, double dy)

第一个版本使用相对于屏幕的 x 和 y 坐标来显示上下文菜单的节点。通常,您会在鼠标点击事件中显示一个上下文菜单,其中MouseEvent对象通过getScreenX()getScreenY()方法向您提供鼠标指针相对于屏幕的坐标。

以下代码片段显示了相对于屏幕坐标系位于(100,100)的画布的上下文菜单:

Canvas canvas = ...
ctxMenu.show(canvas, 100, 100);

第二个版本允许您微调上下文菜单相对于指定的anchor节点的位置。参数side指定了上下文菜单显示在anchor节点的哪一侧。可能的值是Side枚举的常量之一— TOPRIGHTBOTTOMLEFTdxdy参数分别指定相对于anchor节点坐标系的 x 和 y 坐标。这个版本的show()方法需要更多一点的解释。

side参数具有移动anchor节点的 x 轴和 y 轴的作用。轴移动后应用dxdy参数。请注意,当调用此版本的方法时,移动轴只是为了计算上下文菜单的位置。它们不会永久移动,并且anchor节点的位置根本不会改变。图 12-69 显示了side参数值的锚节点及其 x 轴和 y 轴。dxdy参数是该点相对于节点移动后的 x 轴和 y 轴的 x 和 y 坐标。

img/336502_2_En_12_Fig69_HTML.png

图 12-69

使用侧面参数值移动锚节点的 x 轴和 y 轴

注意,side参数的LEFTRIGHT值是基于anchor节点的节点方向来解释的。对于RIGHT_TO_LEFT的节点方向,LEFT值表示节点的右侧。

当您为side参数指定TOPLEFTnull时,dxdy参数相对于节点的原始 x 轴和 y 轴进行测量。当您为side参数指定BOTTOM时,节点的底部成为新的 x 轴,而 y 轴保持不变。当您为side参数指定RIGHT时,节点的右侧成为新的 y 轴,而 x 轴保持不变。

下面对show()方法的调用在anchor节点的左上角显示了一个上下文菜单。side参数的Side.LEFTnull的值将在同一位置显示上下文菜单:

ctxMenu.show(anchor, Side.TOP, 0, 0);

下面对show()方法的调用在anchor节点的左下角显示了一个上下文菜单:

ctxMenu.show(anchor, Side.BOTTOM, 0, 0);

dxdy的值可以是负值。下面对show()方法的调用在anchor节点的左上角显示了一个上下文菜单 10px:

ctxMenu.show(myAnchor, Side.TOP, 0, -10);

如果上下文菜单显示的话,ContextMenu类的hide()方法会隐藏它。通常,当您选择菜单项时,上下文菜单是隐藏的。当上下文菜单使用自定义菜单项且hideOnClick属性设置为 true 时,您需要使用hide()方法。

通常,一个ActionEvent处理程序被添加到上下文菜单的菜单项中。ContextMenu类包含一个onAction属性,这是一个ActionEvent处理程序。如果设置了ContextMenuActionEvent处理程序,则每次激活菜单项时都会调用该处理程序。当菜单项被激活时,您可以使用此ActionEvent来执行后续操作。

清单 12-39 中的程序展示了如何使用上下文菜单。它显示一个Label和一个Canvas。右键单击画布时,会显示一个包含三个菜单项的上下文菜单——矩形、圆形和椭圆形。从菜单项中选择一个形状,在画布上绘制该形状。单击鼠标指针时,将显示上下文菜单。

// ContextMenuTest.java
// ... find in the book's download area.

Listing 12-39Using the ContextMenu Control

使用 CSS 的样式化上下文菜单

一个ContextMenu的默认 CSS 样式类名是context-menu。请参考modena.css文件中定制上下文菜单外观的样式示例。默认情况下,上下文菜单使用投影效果。以下样式将字体大小设置为 8pt,并取消默认效果:

.context-menu {
        -fx-font-size: 8pt;
        -fx-effect: null;
}

了解工具栏控件

ToolBar用于显示一组节点,在屏幕上提供常用的动作项。通常情况下,ToolBar控件包含常用的项目,这些项目也可以通过菜单和上下文菜单获得。

一个ToolBar控件可以保存许多类型的节点。ToolBar中最常用的节点是按钮和切换按钮。Separator s 用于将一组按钮与其他按钮分开。通常,按钮通过使用小图标来保持较小,最好是 16px 乘 16px 的大小。

如果工具栏中的项目溢出,则会出现一个溢出按钮,允许用户导航到隐藏的项目。工具栏可以有水平或垂直方向。水平工具栏将项目水平排列在一行中。垂直工具栏将项目排列在一列中。图 12-70 显示了两个工具栏:一个没有溢出,一个有溢出。有溢出的显示一个溢出按钮(> >)。单击溢出按钮时,会显示隐藏的工具栏项目以供选择。

img/336502_2_En_12_Fig70_HTML.png

图 12-70

带有三个按钮的水平工具栏

您将在本章的示例中使用以下四个ToolBar项目:

Button rectBtn = new Button("", new Rectangle(0, 0, 16, 16));
Button circleBtn = new Button("", new Circle(0, 0, 8));
Button ellipseBtn = new Button("", new Ellipse(8, 8, 8, 6));
Button exitBtn = new Button("Exit");

一个ToolBar控件将项目的引用存储在一个ObservableList<Node>中。使用getItems()方法得到可观察列表的引用。

ToolBar类的默认构造器创建一个空工具栏:

ToolBar toolBar = new ToolBar();
toolBar.getItems().addAll(circleBtn, ellipseBtn, new Separator(), exitBtn);

ToolBar类提供了另一个允许您添加项目的构造器:

ToolBar toolBar = new ToolBar(
        rectBtn, circleBtn, ellipseBtn,
        new Separator(),
        exitBtn);

ToolBar类的orientation属性指定了它的方向:水平或垂直。默认情况下,工具栏使用水平方向。以下代码将其设置为垂直:

// Create a ToolBar and set its orientation to VERTICAL
ToolBar toolBar = new ToolBar();
toolBar.setOrientation(Orientation.VERTICAL);

Tip

默认 CSS 会自动调整工具栏中分隔符的方向。为工具栏中的项目提供工具提示是一种很好的做法,因为它们很小,通常不使用文本内容。

清单 12-40 中的程序展示了如何创建和使用ToolBar控件。它创建了一个工具栏并添加了四个项目。当您点按带有形状的项目之一时,它会在画布上绘制该形状。Exit 项关闭应用程序。

// ToolBarTest.java
// ... find in the book's download area.

Listing 12-40Using the ToolBar Control

用 CSS 设计工具栏的样式

一个ToolBar的默认 CSS 样式类名是tool-bar。它包含一个-fx-orientation CSS 属性,用可能的值水平垂直指定其方向。它支持分别在水平和垂直方向应用的horizontalvertical CSS 伪类。

工具栏使用容器来排列项目。容器是水平方向的HBox和垂直方向的VBox。容器的 CSS 样式类名是container。您可以使用容器的HBoxVBox的所有 CSS 属性。CSS 属性指定了容器中两个相邻项目之间的间距。您可以为工具栏或容器设置该属性。以下两种样式在水平工具栏上具有相同的效果:

.tool-bar  {
        -fx-spacing: 2;
}

.tool-bar > .container  {
        -fx-spacing: 2;
}

工具栏包含一个tool-bar-overflow-button子结构来表示溢出按钮。是一辆StackPanetool-bar-overflow-button包含一个arrow子结构来表示溢出按钮中的箭头。这也是一个StackPane

理解选项卡窗格选项卡

窗口可能没有足够的空间在一个页面视图中显示所有的信息。JavaFX 提供了几个控件来将大量内容分解成多个页面,例如,AccordionPagination控件。TabPaneTab让你在页面上更好地呈现信息。一个Tab代表一个页面,一个TabPane包含了Tab

A Tab不是控件。Tab类的一个实例代表一个TabTab类继承自Object类。然而,Tab像控件一样支持一些特性,例如,它们可以被禁用,使用 CSS 样式化,并且可以有上下文菜单和工具提示。

一个Tab由标题和内容组成。标题由文本、可选图形和关闭选项卡的可选关闭按钮组成。内容由控件组成。通常,控件被添加到一个布局窗格中,该窗格作为其内容被添加到Tab中。

通常,TabPaneTab的标题是可见的。内容区由所有Tabs共享。您需要通过点击标题来选择一个Tab,以查看其内容。在TabPane中,一次只能选择一个选项卡。如果所有选项卡的标题都不可见,则自动显示一个控制按钮,帮助用户选择不可见的选项卡。

TabPane中的Tab可以位于TabPane的顶部、右侧、底部或左侧。默认情况下,它们位于顶部。

图 12-71 显示了一个窗口的两个实例。该窗口包含一个带有两个选项卡的TabPane。在一种情况下,选择常规选项卡,而在另一种情况下,选择地址选项卡。

img/336502_2_En_12_Fig71_HTML.jpg

图 12-71

一个带有TabPane的窗口,其中包含两个选项卡

一个TabPane分为两部分:头区内容区。标题区域显示选项卡的标题;内容区域显示选定选项卡的内容。标题区分为以下几个部分:

  • 标题区域

  • 选项卡标题背景

  • 控制按钮选项卡

  • 标签区域

图 12-72 显示了 a TabPane的部分表头区域。标题区域是整个标题区域。标签标题背景是标签标题所占据的区域。控制按钮选项卡包含当TabPane的宽度不能显示所有选项卡时显示的控制按钮。“控制按钮”选项卡允许您选择当前不可见的选项卡。选项卡区域包含一个Label和一个关闭按钮(选项卡标签旁边的 X 图标)。Label显示标签的文本和图标。“关闭”按钮用于关闭选定的选项卡。

img/336502_2_En_12_Fig72_HTML.png

图 12-72

TabPane报头的不同部分

创建选项卡

您可以使用带有空标题的Tab类的默认构造器创建一个选项卡:

Tab tab1 = new Tab();

使用setText()方法设置标签的标题文本:

tab1.setText("General");

另一个构造器将标题文本作为参数:

Tab tab2 = new Tab("General");

设置选项卡的标题和内容

Tab类包含以下属性,允许您设置标题和内容:

  • text

  • graphic

  • closable

  • content

textgraphicclosable属性指定了标签标题栏中显示的内容。text属性指定一个字符串作为标题文本。graphic属性指定一个节点作为title图标。注意,graphic 属性的类型是Node,因此您可以使用任何节点作为图形。通常,小图标被设置为图形。可以在构造器中设置text属性,或者使用setText()方法。下面的代码片段创建了一个带有文本的选项卡,并将一个图像设置为其图形(假设文件resources/picture/address_icon.png包含在包中):

// Create an ImageView for graphic
String imagePath = "resources/picture/address_icon.png";
URL imageUrl = getClass().getClassLoader().getResource(imagePath);
Image img = new Image(imageUrl.toExternalForm());
ImageView icon = new ImageView(img);

// Create a Tab with "Address" text
Tab addressTab = new Tab("Address");

// Set the graphic
addressTab.setGraphic(icon);

closable属性是一个boolean属性,指定选项卡是否可以关闭。如果设置为 false,则无法关闭选项卡。关闭页签也受TabPane的关闭页签策略控制。如果closable属性设置为 false,则无论TabPane的标签关闭策略如何,用户都不能关闭标签。当我稍后讨论TabPane时,您将了解到标签关闭策略。

content属性是一个指定选项卡内容的节点。当选项卡被选中时,选项卡的内容可见。通常,带有控件的布局窗格被设置为选项卡的内容。以下代码片段创建了一个GridPane,添加了一些控件,并将GridPane设置为选项卡的内容:

// Create a GridPane layout pane with some controls
GridPane grid = new GridPane();
grid.addRow(0, new Label("Street:"), streetFld);
grid.addRow(1, new Label("City:"), cityFld);
grid.addRow(2, new Label("State:"), stateFld);
grid.addRow(3, new Label("ZIP:"), zipFld);

Tab addressTab = new Tab("Address");
addressTab.setContent(grid); // Set the content

创建标签窗格

TabPane类只提供了一个构造器——默认构造器。当您创建TabPane时,它没有选项卡:

TabPane tabPane = new TabPane();

将选项卡添加到选项卡窗格

一个TabPane在一个ObservableList<Tab>中存储其标签的引用。TabPane类的getTabs()方法返回可观察列表的引用。要给TabPane添加标签,你需要把它添加到可观察列表。下面的代码片段向一个TabPane添加了两个选项卡:

Tab generalTab = new Tab("General");
Tab addressTab = new Tab("Address");
...
TabPane tabPane = new TabPane();

// Add the two Tabs to the TabPane
tabPane.getTabs().addAll(generalTab, addressTab);

当一个标签不应该是TabPane的一部分时,你需要把它从可观察列表中移除。TabPane将自动更新其视图:

// Remove the Address tab
tabPane.getTabs().remove(addressTab);

Tab类的只读tabPane属性存储了包含选项卡的TabPane的引用。如果一个标签页还没有被添加到一个TabPane中,它的tabPane属性就是null。使用Tab类的getTabPane()方法获取TabPane的引用。

选项卡选项卡放在一起

我已经介绍了足够的信息,可以让你看到一个TabPaneTab在一起工作。通常,选项卡会被重复使用。从Tab类继承一个类有助于重用标签。清单 12-41 和 12-42 创建了两个Tab类。在后续示例中,您将把它们用作选项卡。GeneralTab类包含输入一个人的名字和出生日期的字段。AddressTab类包含输入地址的字段。

// AddressTab.java
package com.jdojo.control;

import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;

public class AddressTab extends Tab {
        TextField streetFld = new TextField();
        TextField cityFld = new TextField();
        TextField stateFld = new TextField();
        TextField zipFld = new TextField();

        public AddressTab(String text, Node graphic) {
                this.setText(text);
                this.setGraphic(graphic);
                init();
        }

        public void init() {
                GridPane grid = new GridPane();
                grid.addRow(0, new Label("Street:"), streetFld);
                grid.addRow(1, new Label("City:"), cityFld);
                grid.addRow(2, new Label("State:"), stateFld);
                grid.addRow(3, new Label("ZIP:"), zipFld);
                this.setContent(grid);
        }
}

Listing 12-42An AddressTab Class That Inherits from the Tab Class

// GeneralTab.java
package com.jdojo.control;

import javafx.scene.Node;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;

public class GeneralTab extends Tab {
        TextField firstNameFld = new TextField();
        TextField lastNameFld = new TextField();
        DatePicker dob = new DatePicker();

        public GeneralTab(String text, Node graphic) {
                this.setText(text);
                this.setGraphic(graphic);
                init();
        }

        public void init() {
                dob.setPrefWidth(200);
                GridPane grid = new GridPane();
                grid.addRow(0, new Label("First Name:"), firstNameFld);
                grid.addRow(1, new Label("Last Name:"), lastNameFld);
                grid.addRow(2, new Label("DOB:"), dob);
                this.setContent(grid);
        }
}

Listing 12-41A GeneralTab Class That Inherits from the Tab Class

清单 12-43 中的程序创建了两个选项卡。它们是GeneralTabAddressTab类的实例。它们被添加到一个TabPane,后者被添加到一个BorderPane的中心区域。程序显示如图 12-71 所示的窗口。

// TabTest.java
// ... find in the book's download area.

Listing 12-43Using a TabPane and Tabs Together

了解选项卡选择

TabPane支持单一选择模式,一次只能选择一个页签。如果用户或以编程方式选择了一个选项卡,则之前选择的选项卡将被取消选择。Tab类提供了 API 来处理单个选项卡的选择状态。TabPane类提供了允许选择所有标签的 API。

Tab类包含一个boolean类型的只读selected属性。当选项卡被选中时,该值为真。否则就是假的。请注意,这是Tab的财产,而不是TabPane的财产。

Tab允许您添加事件处理程序,当选项卡被选中或取消选中时会得到通知。onSelectionChanged属性存储这样一个事件的引用:

Tab generalTab = ...
generalTab.setOnSelectionChanged(e -> {
        if (generalTab.isSelected()) {
                System.out.println("General tab has been selected.");
        } else {
                System.out.println("General tab has been unselected.");
        }
});

TabPane跟踪选中的选项卡及其在选项卡列表中的索引。为此,它使用了一个单独的对象,称为选择模型TabPane类包含一个selectionModel属性来存储选项卡选择细节。该属性是SingleSelectionModel类的一个对象。你可以使用你自己的选择模型,这个模型几乎是不需要的。选择模型提供了与选择相关的功能:

  • 它允许您使用选项卡的索引来选择选项卡。第一个选项卡的索引为零。

  • 它允许您选择列表中的第一个、下一个、上一个或最后一个选项卡。

  • 它允许您清除选择。请注意,此功能是可用的,但并不常用。一个TabPane通常应该总是有一个选中的选项卡。

  • selectedIndexselectedItem属性跟踪所选选项卡的索引和引用。您可以向这些属性添加一个ChangeListener,以处理TabPane中选项卡选择的变化。

默认情况下,TabPane选择它的第一个标签。下面的代码片段选择了一个TabPane中的最后一个Tab:

tabPane.getSelectionModel().selectLast();

使用选择模型的selectNext()方法从列表中选择下一个选项卡。当已经选择了最后一个选项卡时调用此方法没有任何效果。

使用selectPrevious()selectLast()方法选择列表中的前一个和最后一个选项卡。select(int index)select(T item)方法使用选项卡的索引和引用来选择选项卡。

清单 12-44 中的程序给一个TabPane添加了两个标签。它向两个选项卡添加了一个选择更改事件处理程序。在TabPaneselectionModel属性的selectedItem属性中增加一个ChangeListener。做出选择后,详细的消息会打印在标准输出上。请注意,运行应用程序时会打印一条消息,因为默认情况下,TabPane选择模型会选择第一个选项卡。

// TabSelection.java
// ... find in the book's download area.

Listing 12-44Tracking Tab Selection in a TabPane

关闭选项卡窗格中的选项卡

有时,用户需要按需添加标签到TabPane中,并且他们也应该能够关闭标签。例如,所有现代的网络浏览器都使用标签来浏览,并允许你打开和关闭标签。按需添加选项卡需要在 JavaFX 中进行一些编码。然而,用户关闭标签是内置在TabTabPane类中的。

用户可以使用出现在Tab s 标题栏中的关闭按钮关闭TabPane中的Tab。标签关闭功能由以下属性控制:

  • Tab类的closable属性

  • TabPane类的tabClosingPolicy属性

一个Tab类的closable属性指定标签是否可以被关闭。如果设置为 false,则无论tabClosingPolicy的值如何,选项卡都不能关闭。属性的默认值为 true。tabClosingPolicy属性指定制表符关闭按钮如何可用。它的值是TabPane.TabClosingPolicy枚举的下列常量之一:

  • ALL_TABS

  • SELECTED_TAB

  • UNAVAILABLE

ALL_TABS表示关闭按钮对所有选项卡都可用。也就是说,只要选项卡的closable属性为真,任何选项卡都可以随时关闭。SELECTED_TAB表示关闭按钮只对选中的标签页出现。也就是说,任何时候都只能关闭选定的选项卡。这是TabPane的默认关闭标签策略。UNAVAILABLE表示关闭按钮对任何标签都不可用。也就是说,使用者不能关闭任何翼片,不管它们的可关闭特性如何。

必须区分以下两种情况:

  • 用户使用关闭按钮关闭标签

  • 通过从TabPaneTab的可观察列表中移除它们来以编程方式移除它们

两者具有相同的效果,即从TabPane中移除Tab s。本节中的讨论适用于用户关闭标签。

可以否决关闭选项卡的用户操作。您可以为选项卡的TAB_CLOSE_REQUEST_EVENT事件添加事件处理程序。当用户试图关闭选项卡时,会调用事件处理程序。如果事件处理程序使用事件,关闭操作将被取消。您可以使用Tab类的onCloseRequest属性来设置这样一个事件:

Tab myTab = new Tab("My Tab");
myTab.setOnCloseRequest(e -> {
    if (SOME_CONDITION_IS_TRUE) {
       // Cancel the close request
       e.consume();
   }
});

当用户关闭选项卡时,它也会生成一个关闭事件。使用Tab类的onClosed属性设置选项卡的关闭事件处理程序。事件处理程序通常用于释放选项卡持有的资源:

myTab.setOnClosed(e -> {/* Release tab resources here */});

清单 12-45 中的程序展示了如何使用标签关闭相关的属性和事件。它在一个TabPane中显示两个选项卡。一个复选框允许您否决选项卡的关闭。除非选中该复选框,否则在 close 请求事件中,关闭选项卡的尝试将被否决。如果关闭了选项卡,可以使用“恢复选项卡”按钮恢复它们。使用标签关闭策略ChoiceBox使用不同的标签关闭策略。例如,如果您选择UNAVAILABLE作为标签页关闭策略,关闭按钮将从所有标签页中消失。当标签关闭时,在标准输出上打印一条消息。

// TabClosingTest.java
// ... find in the book's download area.

Listing 12-45Using Properties and Events Related to Closing Tabs by Users

选项卡窗格中定位选项卡

TabPane中的标签可以位于顶部、右侧、底部或左侧。TabPaneside属性指定了制表符的位置。它被设置为Side枚举的常量之一:

  • TOP

  • RIGHT

  • BOTTOM

  • LEFT

侧属性的默认值是Side.TOP。下面的代码片段创建了一个 T abPane,并将 side 属性设置为Side.LEFT,以便在左侧放置制表符:

TabPane tabPane = new TabPane();
tabPane.setSide(Side.LEFT);

Tip

选项卡的实际位置也使用节点方向。例如,如果将side属性设置为Side.LEFT并将TabPane的节点方向设置为RIGHT_TO_LEFT,则选项卡将位于右侧。

TabPane类包含一个rotateGraphic属性,它是一个boolean属性。该属性与side属性相关。当side属性为Side.TOPSide.BOTTOM时,标题栏中所有页签的图形都处于垂直位置。默认情况下,当side属性变为Side.LEFTSide.RIGHT时,标题文本会旋转,使图形保持垂直。rotateGraphic属性指定图形是否随文本旋转,如下面的代码所示。默认情况下,它被设置为 false。

// Rotate the graphic with the text for left and right sides
tabPane.setRotateGraphic(true);

图 12-73 显示了侧属性设置为TOPLEFT的 TabPane 中选项卡的标题栏。注意当边属性为LEFT并且rotateGraphic属性为假和真时对图形的影响。当选项卡位于顶部或底部时,rotateGraphic属性不起作用。

img/336502_2_En_12_Fig73_HTML.jpg

图 12-73

TabPane的侧面和rotateGraphic属性的影响

选项卡窗格中调整选项卡的大小

TabPane将其布局分为两部分:

  • 标题区域

  • 内容区域

标题区域显示选项卡的标题。内容区域显示选定选项卡的内容。内容区域的大小是根据所有选项卡的内容自动计算的。TabPane包含以下属性,允许您设置选项卡标题栏的最小和最大尺寸:

  • tabMinHeight

  • tabMaxHeight

  • tabMinWidth

  • tabMaxWidth

最小宽度和高度的默认值为零,最大宽度和高度的默认值为Double.MAX_VALUE。默认大小是根据选项卡标题的上下文计算的。如果希望所有选项卡标题都是固定大小,请将最小和最大宽度和高度设置为相同的值。请注意,对于固定大小的选项卡,标题栏中较长的文本将被截断。

下面的代码片段创建了一个TabPane并设置了属性,因此所有的选项卡都是 100 像素宽,30 像素高:

TabPane tabPane = new TabPane();
tabPane.setTabMinHeight(30);
tabPane.setTabMaxHeight(30);
tabPane.setTabMinWidth(100);
tabPane.setTabMaxWidth(100);

使用嵌入式和浮动式标签面板

TabPane可以处于隐藏或浮动模式。默认模式是隐藏模式。在凹进模式下,它看起来被固定。在浮动模式下,它的外观被改变以使它看起来像是浮动的。在浮动模式下,标题区域的背景色被删除,内容区域的边框被添加。以下是决定使用哪种模式的经验法则:

  • 如果你在一个窗口中使用一个TabPane和其他控件,使用浮动模式。

  • 如果TabPane是窗口上唯一的控件,使用隐藏模式。

图 12-74 显示了两个具有相同TabPane的窗口:一个在隐藏模式,一个在浮动模式。

img/336502_2_En_12_Fig74_HTML.jpg

图 12-74

凹进和浮动模式下的 A TabPane

一个TabPane的浮动模式是由一个样式类指定的。TabPane类包含一个STYLE_CLASS_FLOATING常量。如果您将这个样式类添加到一个TabPane,它将处于浮动模式。否则,它处于凹进模式。下面的代码片段显示了如何打开和关闭TabPane的浮动模式:

TabPane tabPane = new TabPane();

// Turn on the floating mode
tabPane.getStyleClass().add(TabPane.STYLE_CLASS_FLOATING);
...
// Turn off the floating mode
tabPane.getStyleClass().remove(TabPane.STYLE_CLASS_FLOATING);

带有 CSS 的样式选项卡选项卡

一个tab和一个TabPane的默认 CSS 样式类名是tab-pane。你可以直接使用tab风格类或者使用TabPane的子结构来设计Tab的风格。通常使用后一种方法。

TabPane支持四个 CSS 伪类,对应于其side属性的四个值:

  • top

  • right

  • bottom

  • left

您可以使用以下 CSS 属性设置TabPane中标签标题的最小和最大尺寸。它们对应于TabPane类中的四个属性。有关这些属性的详细讨论,请参考“?? 选项卡窗格中的尺寸选项卡”一节:

  • -fx-tab-min-width

  • -fx-tab-max-width

  • -fx-tab-min-height

  • -fx-tab-max-height

A TabPane将其布局边界分为两个区域:标题区域和内容区域。请参考图 12-72 了解割台区域的不同子部件。标题区域称为tab-header-area子结构,它包含以下子结构:

  • headers-region

  • tab-header-background

  • control-buttons-tab

  • tab

control-buttons-tab子结构包含一个tab-down-button子结构,后者包含一个arrow子结构。tab子结构包含tab-labeltab-close-button子结构。tab-content-area子结构代表TabPane的内容区域。子结构让您可以设计TabPane的不同部分。

TabPane处于浮动模式时,下面的代码删除标题区域的背景颜色:

.tab-pane > .tab-header-area > .tab-header-background {
    -fx-background-color: null;
}

以下代码以粗体显示选定选项卡的文本。注意选择器.tab:selected中选项卡的所选伪类的使用:

.tab-pane > .tab-header-area > .headers-region > .tab:selected
> .tab-container > ,tab-label {
        -fx-font-weight: bold;
}

以下代码显示了蓝色背景的TabPane中的Tab和 10pt 白色标题文本:

.tab-pane > .tab-header-area > .headers-region > .tab  {
    -fx-background-color: blue;
}

.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container
> .tab-label {
        -fx-text-fill: white;
        -fx-font-size: 10pt;
}

为浮动模式设计样式时,使用TabPanefloating样式类。以下样式在浮动模式下将边框颜色设置为蓝色:

.tab-pane.floating > .tab-content-area {
        -fx-border-color: blue;
}

请参考modena.css文件,了解用于TabPane的完整样式列表。

了解 HTMLEditor 控件

HTMLEditor控件为 JavaFX 应用程序提供了丰富的文本编辑功能。它使用 HTML 作为它的数据模型。也就是说,HTMLEditor中的格式化文本是以 HTML 格式存储的。一个HTMLEditor控件可以用于在业务应用程序中输入格式化的文本,例如,产品描述或评论。它还可以用于在电子邮件客户端应用程序中输入电子邮件内容。图 12-75 显示了一个带有HTMLEditor控件的窗口。

img/336502_2_En_12_Fig75_HTML.jpg

图 12-75

一个控件

一个HTMLEditor用它显示格式工具栏。您不能隐藏工具栏。它们可以使用 CSS 来设置样式。使用工具栏,您可以

  • 使用系统剪贴板复制、剪切和粘贴文本

  • 应用文本对齐

  • 缩进文本

  • 应用项目符号列表和编号列表样式

  • 设置前景色和背景色

  • 使用字体系列和字体大小应用段落和标题样式

  • 应用格式样式,如粗体、斜体、下划线和删除线

  • 添加水平标尺

该控件支持 HTML5。请注意,工具栏不允许您应用所有类型的 HTML。但是,如果您加载使用这些样式的文档,它允许您编辑它们。例如,您不能直接在控件中创建 HTML 表。但是,如果将包含 HTML 表格的 HTML 内容加载到控件中,您将能够编辑表格中的数据。

HTMLEditor没有提供 API 来从文件中加载 HTML 内容,以将其内容保存到文件中。您必须编写自己的代码来实现这一点。

创建一个html 编辑器

一个HTMLEditor类的实例代表一个HTMLEditor控件。该类包含在javafx.scene.web包中。使用默认的构造器,这是提供的唯一构造器,来创建一个HTMLEditor:

HTMLEditor editor = new HTMLEditor();

使用html 编辑器

HTMLEditor类有一个非常简单的 API,只包含三个方法:

  • getHtmlText()

  • setHtmlText(String htmlText)

  • print(PrinterJob job)

getHTMLText()方法以字符串的形式返回 HTML 内容。方法将控件的内容设置为指定的 HTML 字符串。print()方法打印控件的内容。

清单 12-46 中的程序展示了如何使用HTMLEditor。它显示一个HTMLEditor,一个TextArea,和两个Buttons。您可以使用按钮将HTMLEditor中的文本转换成 HTML 代码,反之亦然。

// HTMLEditorTest.java
// ... find in the book's download area.

Listing 12-46Using the HTMLEditor Control

使用 CSS 对 html 编辑器进行样式化

一个HTMLEditor的默认 CSS 样式类名是html-editorHTMLEditor使用Control的样式,比如填充、边框和背景色。

您可以分别设置工具栏中每个按钮的样式。以下是工具栏按钮的样式类名列表。名称是不言自明的,例如,html-editor-align-righthtml-editor-hr分别是用于文本右对齐和绘制水平标尺的工具栏按钮的样式类名称。

  • html-editor-cut

  • html-editor-copy

  • html-editor-paste

  • html-editor-align-left

  • html-editor-align-center

  • html-editor-align-right

  • html-editor-align-justify

  • html-editor-outdent

  • html-editor-indent

  • html-editor-bullets

  • html-editor-numbers

  • html-editor-bold

  • html-editor-italic

  • html-editor-underline

  • html-editor-strike

  • html-editor-hr

以下代码为工具栏中的“剪切”按钮设置自定义图像:

.html-editor-cut {
        -fx-graphic: url("my_html_editor_cut.jpg");
}

如果要将样式应用于所有工具栏按钮和切换按钮,请使用buttontoggle-button样式类名:

/* Set the background colors for all buttons and toggle buttons */
.html-editor .button, .html-editor .toggle-button {
    -fx-background-color: lightblue;
}

HTMLEditor显示两个ColorPicker供用户选择背景色和前景色。他们的风格类名是html-editor-backgroundhtml-editor-foreground。下面的代码显示了在ColorPicker中选择的颜色标签:

.html-editor-background {
    -fx-color-label-visible: true;
}

.html-editor-foreground {
    -fx-color-label-visible: true;
}

选择文件和目录

JavaFX 在javafx.stage包中提供了用于显示文件和目录对话框的FileChooserDirectoryChooser类。这些对话框具有依赖于平台的外观,并且不能使用 JavaFX 来设计样式。他们是而不是的控制者。我在本章中讨论它们是因为它们通常和控件一起使用。例如,单击按钮时会显示文件或目录对话框。在某些平台上,例如某些移动和嵌入式设备,用户可能无法访问文件系统。使用这些类来访问这些设备上的文件和目录没有任何作用。

文件选择器对话框

一个FileChooser是标准的文件对话框。它用于让用户选择要打开或保存的文件。它的某些部分,例如标题、初始目录和文件扩展名列表,可以在打开对话框之前指定。使用文件对话框有三个步骤:

  1. 创建一个FileChooser类的对象。

  2. 设置文件对话框的初始属性。

  3. 使用showXXXDialog()方法之一显示特定类型的文件对话框。

创建文件对话框

FileChooser类的一个实例用于打开文件对话框。该类包含一个无参数构造器来创建其对象:

// Create a file dialog
FileChooser fileDialog = new FileChooser();

设置对话框的初始属性

您可以设置文件对话框的以下初始属性:

  • Title

  • initialDirectory

  • initialFileName

  • 扩展过滤器

FileChooser类的title属性是一个 s t环,代表文件对话框的标题:

// Set the file dialog title
fileDialog.setTitle("Open Resume");

FileChooser类的initialDirectory属性是一个File,代表显示文件对话框时的初始目录:

// Set C:\ as initial directory (on Windows)
fileDialog.setInitialDirectory(new File("C:\\"));

FileChooser类的initialFileName属性是一个字符串,它是文件对话框的初始文件名。通常,它用于文件保存对话框。如果用于文件打开对话框,其效果取决于平台。例如,在 Windows 上它会被忽略:

// Set the initial file name
fileDialog.setInitialFileName("untitled.htm");

您可以为文件对话框设置扩展过滤器列表。过滤器显示为下拉框。一次只有一个过滤器处于活动状态。“文件”对话框仅显示与活动扩展名筛选匹配的文件。扩展过滤器由ExtensionFilter类的实例表示,它是FileChooser类的内部静态类。FileChooser类的getExtensionFilters()方法返回一个ObservableList<FileChooser.ExtensionFilter>。将扩展过滤器添加到列表中。扩展名过滤器有两个属性:一个描述和一个文件扩展名列表,格式为*.<extension>:

import static javafx.stage.FileChooser.ExtensionFilter;
...
// Add three extension filters
fileDialog.getExtensionFilters().addAll(
        new ExtensionFilter("HTML Files", "*.htm", "*.html"),
        new ExtensionFilter("Text Files", "*.txt"),
        new ExtensionFilter("All Files", "*.*"));

默认情况下,当显示文件对话框时,列表中的第一个扩展名筛选器处于活动状态。使用selectedExtensionFilter属性指定文件对话框打开时的初始活动过滤器:

// Continuing with the above snippet of code, select *.txt filter by default
fileDialog.setSelectedExtensionFilter(
    fileDialog.getExtensionFilters().get(1));

同一个selectedExtensionFilter属性包含文件对话框关闭时用户选择的扩展过滤器。

显示对话框

一个FileChooser类的实例可以打开三种类型的文件对话框:

  • 仅选择一个文件的文件打开对话框

  • 用于选择多个文件的文件打开对话框

  • 文件保存对话框

下面三个FileChooser类的方法用于打开三种类型的文件对话框:

  • showOpenDialog(Window ownerWindow)

  • showOpenMultipleDialog(Window ownerWindow)

  • showSaveDialog(Window ownerWindow)

直到文件对话框关闭,这些方法才返回。您可以将null指定为所有者窗口。如果指定了所有者窗口,当显示文件对话框时,所有者窗口的输入将被阻止。

showOpenDialog()showSaveDialog()方法返回一个File对象,它是被选择的文件,如果没有选择文件,则返回nullshowOpenMultipleDialog()方法返回一个List<File>,它包含所有选择的文件,或者如果没有选择文件,返回null:

// Show a file open dialog to select multiple files
List<File> files = fileDialog.showOpenMultipleDialog(primaryStage);
if (files != null) {
        for(File f : files) {
                System.out.println("Selected file :" + f);
        }
} else {
        System.out.println("No files were selected.");
}

使用FileChooser类的selectedExtensionFilter属性在文件对话框关闭时获取选定的扩展过滤器:

import static javafx.stage.FileChooser.ExtensionFilter;
...
// Print the selected extension filter description
ExtensionFilter filter = fileDialog.getSelectedExtensionFilter();
if (filter != null) {
    System.out.println("Selected Filter: " + filter.getDescription());
} else {
        System.out.println("No extension filter selected.");
}

使用文件对话框

清单 12-47 中的程序展示了如何使用打开和保存文件对话框。它显示一个带有HTMLEditor和三个按钮的窗口。使用“打开”按钮在编辑器中打开 HTML 文件。在编辑器中编辑内容。使用“保存”按钮将编辑器中的内容保存到文件中。如果您在“保存简历”对话框中选择了一个现有文件,该文件的内容将被覆盖。它留给读者作为增强程序的一个练习,所以它会在覆盖现有文件之前提示用户。

// FileChooserTest.java
// ... find in the book's download area.

Listing 12-47Using Open and Save File Dialogs

目录选择器对话框

有时,您可能需要让用户浏览计算机上可用文件系统中的目录。DirectoryChooser类让你显示一个依赖于平台的目录对话框。

DirectoryChooser类包含两个属性:

  • title

  • initialDirectory

title属性是一个字符串,是目录对话框的标题。initialDirectory属性是一个File,是对话框显示时对话框中选择的初始目录。

使用DirectoryChooser类的showDialog(Window ownerWindow)方法打开目录对话框。当对话框打开时,您最多可以选择一个目录,或者关闭对话框而不选择目录。该方法返回一个File,它是被选择的目录,如果没有选择目录,则返回null。该方法被阻止,直到对话框关闭。如果指定了所有者窗口,当对话框显示时,所有者窗口链中所有窗口的输入都被阻止。您可以指定一个空的所有者窗口。

以下代码片段显示了如何创建、配置和显示目录对话框:

DirectoryChooser dirDialog = new DirectoryChooser();

// Configure the properties
dirDialog.setTitle("Select Destination Directory");
dirDialog.setInitialDirectory(new File("c:\\"));

// Show the directory dialog
File dir = dirDialog.showDialog(null);
if (dir != null) {
        System.out.println("Selected directory: " + dir);
} else {
        System.out.println("No directory was selected.");
}

摘要

用户界面是一种在应用程序及其用户之间交换输入和输出信息的手段。使用键盘输入文本、使用鼠标选择菜单项以及单击按钮都是向 GUI 应用程序提供输入的示例。该应用程序使用文本、图表和对话框等在计算机显示器上显示输出。用户使用称为控件小部件的图形元素与 GUI 应用程序进行交互。按钮、标签、文本字段、文本区域、单选按钮和复选框是控件的几个例子。JavaFX 提供了一组丰富的易于使用的控件。控件被添加到布局窗格中,对它们进行定位和调整大小。

JavaFX 中的每个控件都由一个类的实例来表示。控制类包含在javafx.scene.control包中。JavaFX 中的控件类是Control类的一个直接或间接的子类,而后者又继承自Region类。回想一下,Region类继承自Parent类。所以,技术上来说,一个Control也是一个Parent。一个Parent可以生孩子。但是,控件类不允许添加子级。通常,控件由内部维护的多个节点组成。控件类通过返回一个ObservableList<Node>getChildrenUnmodifiable()方法公开其内部不可修改的子类列表。

一个labeled控件包含一个只读的文本内容和一个可选的图形作为其用户界面的一部分。LabelButtonCheckBoxRadioButtonHyperlink是 JavaFX 中标签控件的一些例子。所有带标签的控件都直接或间接地继承自Labeled类,而后者又继承自Control类。Labeled类包含所有标签控件共有的属性,例如内容对齐、文本相对于图形的位置以及文本字体。

JavaFX 提供了按钮控件,可用于执行命令和/或做出选择。所有按钮控件类都继承自ButtonBase类。所有类型的按钮都支持ActionEvent。按钮被激活时会触发一个ActionEvent。可以用不同的方式激活按钮,例如,使用鼠标、助记键、加速键或其他组合键。被激活时执行命令的按钮称为命令按钮。ButtonHyperlinkMenuButton类代表命令按钮。一个MenuButton让用户执行命令列表中的一个命令。用于向用户呈现不同选择的按钮被称为选择按钮。ToggleButtonCheckBoxRadioButton类代表选择按钮。第三种按钮是前两种的混合。它们让用户执行命令或做出选择。SplitMenuButton类代表一个混合按钮。

JavaFX 提供了允许用户从项目列表中选择项目的控件。与按钮相比,它们占用更少的空间。这些控件是ChoiceBoxComboBoxListViewColorPickerDatePicker. ChoiceBox,用户可以从预定义项目的小列表中选择一个项目。ComboBoxChoiceBox的高级版本。它有许多特性,例如,可编辑的能力或改变列表中项目的外观,这在ChoiceBox. ListView中没有提供,为用户提供了从项目列表中选择多个项目的能力。通常情况下,用户始终可以看到ListView中的所有或多个项目。ColorPicker允许用户从标准调色板中选择一种颜色,或以图形方式定义自定义颜色。DatePicker允许用户从日历弹出窗口中选择日期。用户可以选择以文本形式输入日期。ComboBoxColorPickerDatePicker具有相同的超类,即ComboBoxBase类。

文本输入控件允许用户使用单行或多行纯文本。所有文本输入控件都继承自TextInputControl类。有三种类型的文本输入控件:TextFieldPasswordFieldTextAreaTextField让用户输入单行纯文本;文本中的换行符和制表符被删除。PasswordField继承自TextField。它的工作方式与TextField非常相似,只是它屏蔽了文本。TextArea允许用户输入多行纯文本。一个换行符在TextArea中开始一个新的段落。

对于长时间运行的任务,您需要向用户提供指示任务进度的视觉反馈,以获得更好的用户体验。ProgressIndicatorProgressBar控件用于显示任务的进度。它们显示进度的方式不同。ProgressBar类继承自ProgressIndicator类。ProgressIndicator在圆形控件中显示进度,而ProgressBar使用水平条。

TitledPane是带标签的控件。它将文本显示为标题。该图形显示在标题栏中。除了文本和图形,它还有内容,这是一个节点。通常,一组控件被放在一个容器中,该容器被添加为TitledPane的内容。TitledPane可处于折叠或展开状态。在折叠状态下,它只显示标题栏并隐藏内容。在展开状态下,它显示标题栏和内容。

Accordion是显示一组TitledPane控件的控件,其中一次只有一个控件处于展开状态。

Pagination是一个控件,用于通过将一个大的单一内容分成称为页面的小块来显示它,例如,搜索的结果。

工具提示是一个弹出控件,用于显示节点的附加信息。当鼠标指针悬停在节点上时,它会显示出来。当鼠标指针悬停在某个节点上时和显示该节点的工具提示时之间会有一小段延迟。工具提示在一小段时间后隐藏。当鼠标指针离开控件时,它也被隐藏。你不应该设计一个 GUI 应用程序,在那里用户依赖于看到控件的工具提示,因为如果鼠标指针从不停留在控件上,它们可能根本不会显示。

ScrollBarScrollPane控件为其他控件提供滚动功能。这些控件不能单独使用。它们总是用于支持其他控件中的滚动。

有时,您希望将逻辑上相关的控件水平或垂直并排放置。为了获得更好的外观,控件使用不同类型的分隔符进行分组。SeparatorSplitPane控件用于在视觉上区分两个控件或两组控件。

Slider控件允许用户通过沿轨迹滑动拇指(或旋钮)从数值范围中选择一个数值。一个Slider可以是水平的,也可以是垂直的。

菜单用于以紧凑的形式向用户提供可操作项目的列表。菜单栏是作为菜单容器的水平栏。MenuBar类的一个实例代表一个菜单栏。一个menu包含一个可操作项目的列表,例如通过点击它来按需显示。当用户选择一个项目或将鼠标指针移出列表时,菜单项列表隐藏。菜单通常作为子菜单添加到菜单栏或其他菜单中。Menu类的一个实例代表一个菜单。一个Menu显示文本和图形。菜单项是菜单中可操作的项目。与菜单项相关联的动作由鼠标或按键来执行。菜单项可以使用 CSS 样式。MenuItem类的一个实例代表一个菜单项。MenuItem类不是一个节点。它继承自Object类,因此不能直接添加到场景图中。你需要把它加到一个Menu里。

ContextMenu是一个弹出控件,根据请求显示菜单项列表。它被称为上下文菜单或弹出菜单。默认情况下,它是隐藏的。用户必须提出请求,通常是通过右击鼠标按钮来显示它。一旦做出选择,它将被隐藏。用户可以通过按 Esc 键或在上下文菜单边界外单击来关闭上下文菜单。ContextMenu类的一个对象代表一个上下文菜单。

ToolBar用于显示一组节点,在屏幕上提供常用的动作项。通常情况下,ToolBar包含常用的项目,这些项目也可以通过菜单和上下文菜单获得。一个ToolBar可以容纳许多类型的节点。ToolBar中最常用的节点是按钮和切换按钮。Separator s 用于将一组按钮与其他按钮分开。通常,按钮通过使用小图标来保持较小,最好是 16px 乘 16px 的大小。

窗口可能没有足够的空间在单页视图中显示所有的信息。s 和 ?? 可以让你更好地在页面中呈现信息。一个Tab代表一个页面,一个TabPane包含选项卡。一个Tab不是控制。Tab类的一个实例代表一个TabTab类继承自Object类。然而,一个Tab像控件一样支持一些特性,例如,它们可以被禁用,使用 CSS 样式,并且有上下文菜单和工具提示。

一个Tab由标题和内容组成。标题由文本、可选图形和关闭选项卡的可选关闭按钮组成。内容由控件组成。通常,TabPane中标签的标题是可见的。内容区域由所有选项卡共享。TabPane中的Tab可能位于TabPane的顶部、右侧、底部或左侧。默认情况下,它们位于顶部。

HTMLEditor控件为 JavaFX 应用程序提供了丰富的文本编辑功能。它使用 HTML 作为它的数据模型。也就是说,HTMLEditor中的格式化文本是以 HTML 格式存储的。

JavaFX 在javafx.stage包中提供了FileChooserDirectoryChooser类,分别用于显示文件和目录对话框。这些对话框具有依赖于平台的外观,并且不能使用 JavaFX 来设计样式。它们不是控制。一个FileChooser是一个标准的文件对话框。它用于让用户选择要打开或保存的文件。一个DirectoryChooser让用户从机器上可用的文件系统中浏览一个目录。

下一章将讨论用于以表格格式显示和编辑数据的TableView控件。