vlambda博客
学习文章列表

JDK17 |java17学习 第 11 章 网络编程

Chapter 12: Java GUI Programming

本章概述了 Java 图形用户界面 (GUI) 技术,并演示了如何使用 JavaFX 工具包创建一个图形用户界面应用程序。最新版本的 JavaFX 不仅提供了许多有用的功能,而且还允许保留和嵌入遗留的实现和样式。

在某些方面,GUI 是应用程序中最重要的部分。它直接与用户交互。如果 GUI 不方便、不吸引眼球或令人困惑,即使是最好的后端解决方案也可能无法说服用户使用此应用程序。相比之下,经过深思熟虑、直观且设计精美的 GUI 有助于留住用户,即使应用程序的工作不如其竞争对手。

本章的议程要求我们涵盖以下主题:

  • Java GUI 技术
  • JavaFX 基础知识
  • 带有 JavaFX 的 HelloWorld
  • 控制元件
  • 图表
  • 应用 CSS
  • 使用 FXML
  • 嵌入 HTML
  • 播放媒体
  • 添加效果

在本章结束时,您将能够使用 Java GUI 技术创建用户界面,以及创建用户界面项目并将其用作独立应用程序。

Technical requirements

为了能够执行本章提供的代码示例,您将需要以下内容:

  • 装有 Microsoft Windows、Apple macOS 或 Linux 操作系统的计算机
  • Java SE 版本 17 或更高版本
  • 您选择的 IDE 或代码编辑器

第 1 章Java 17 入门。本章的代码示例文件可在 GitHub 上的 https://github .com/PacktPublishing/Learn-Java-17-Programming.gitexamples/src/main/java/com/packt/learnjava/ch12_gui 文件夹和gui 文件夹,其中包含一个独立的 GUI 应用程序。

Java GUI technologies

Java Foundation Classes (JFC) 的名称可能是一个来源 非常混乱。它暗示 Java 基础的类,而事实上,JFC 只包括与 GUI 相关的类和接口。准确地说,JFC 是三个框架的集合:Abstract Window Toolkit (AWT)、Swing 和 Java 2D。

JFC 是 Java 类库 (JCL) 的 的一部分,虽然名称JFC 于 1997 年才成立,而 AWT 从一开始就是 JCL 的一部分。当时,Netscape 开发了一个名为 Internet Foundation Classes (IFC) 的 GUI 库,以及 Microsoft 为 GUI 创建了应用程序基础类 (AFC)发展,也是。因此,当 Sun Microsystems 和 Netscape 决定组建一个新的 GUI 库时,他们继承了 Foundation 这个词并创建了 JFC。 Swing 框架从 AWT 接管了 Java GUI 编程,并成功使用了近二十年。

Java 8 中的 JCL 中添加了一个新的 GUI 编程工具包 JavaFX。它在 Java 11 中从 JCL 中删除,从那时起,作为 Gluon 公司支持的开源项目,作为 JDK 之外的可下载模块. JavaFX 使用与 AWT 和 Swing 不同的 GUI 编程方法。它提供了更一致的 和更简单的设计,并且很有可能成为获胜的 Java GUI 编程工具包。

JavaFX fundamentals

纽约、伦敦、巴黎和莫斯科等城市有许多剧院,居住在那里的人们几乎每周都会听到有关新剧和作品的消息。这让他们不可避免地熟悉戏剧术语,其中的术语stagescene event 可能是最常用的。这三个术语也是 JavaFX 应用程序结构的基础。

JavaFX 中包含所有其他组件的顶级容器由 javafx.stage.Stage 类表示。因此,您可以说,在 JavaFX 应用程序中,一切都发生在 stage 上。从用户的角度来看,它是一个显示区域或窗口,所有控件和组件都在其中执行其操作(就像剧院中的演员)。而且,与剧院中的演员类似,他们是在 scene 的上下文中进行的,由 javafx.scene.Scene 表示代码>类。因此,JavaFX 应用程序,就像剧院中的戏剧一样,由 Scene 对象组成,这些对象分别呈现在 Stage 对象中时间。每个 Scene 对象都包含一个图形,该图形定义了 节点)的位置>JavaFX:控件、布局、组、形状等。它们中的每一个都扩展了抽象类 javafx.scene.Node

一些节点的控件与事件相关联:例如,单击按钮或选中复选框。这些事件可以由与相应控制元素关联的事件处理程序处理。

JavaFX 应用程序的主类必须扩展抽象的 java.application.Application 类,该类具有多个生命周期方法。我们按调用顺序列出它们:launch()init() notifyPreloader()start()stop()。看起来有很多要记住的。但是,最有可能的是,您只需要实现一种方法,start(),实际的 GUI 在此构造和执行。尽管如此,为了完整性,我们将回顾所有生命周期方法:

  • static void launch(Class appClass, String... args):这会启动应用程序,通常称为 main< /代码>方法;在调用 Platform.exit() 或所有应用程序窗口关闭之前,它不会返回。 appClass 参数必须是具有公共无参数构造函数的 Application 类的公共子类。
  • static void launch(String... args):同上方法,假设Application的公共子类class 是直接封闭的类。这是最常用于启动 JavaFX 应用程序的方法;我们也将在示例中使用它。
  • void init():该方法在Application类加载完成后调用;它通常用于某种资源初始化。默认实现什么都不做,我们也不会使用它。
  • void notifyPreloader(Preloader.PreloaderNotification info):这个可以用来在初始化耗时较长时显示进度;我们 不会使用它。
  • abstract void start(Stage primaryStage):我们要实现的方法。在 init() 方法返回后,系统准备好执行主要工作后调用它。 primaryStage 参数是应用程序要呈现其场景的阶段。
  • void stop():当应用程序停止时调用,可用于释放资源。默认实现什么都不做,我们也不会使用它。

JavaFX工具包的API可以在网上找到(https: //openjfx.io/javadoc/18//)。截至撰写本文时,最新版本是 18。 Oracle 也提供了大量的文档和代码示例(https://docs.oracle.com/javafx/2 //)。该文档包括 Scene Builder(一种提供可视化布局环境的开发工具,让您无需编写任何代码即可快速为 JavaFX 应用程序设计用户界面)的描述和用户手册。这个工具对于创建复杂的 GUI 可能很有用,而且很多人一直都在使用它。不过,在本书中,我们将专注于在不使用该工具的情况下编写 JavaFX 代码。

为了能够做到这一点,以下是必要的步骤:

  1. 将以下依赖项添加到 pom.xml 文件中:
    <依赖>    <groupId>org.openjfx</groupId>    <artifactId>javafx-controls</artifactId>    <版本>18</版本> </依赖> <依赖性>    <groupId>org.openjfx</groupId>    <artifactId>javafx-fxml</artifactId>    <版本>18</版本> </dependency>
  2. JavaFX SDK target="_blank">https://gluonhq.com/products/javafx/(openjfx-18_osx-x64_bin-sdk.zip 文件,截至写作时间)并将其解压缩到任何目录中。
  3. 假设您已将 JavaFX SDK 解压缩到 /path/javafx-sdk/ 文件夹中,请将以下选项添加到 Java 命令中,这将在 Linux 平台上启动您的 JavaFX 应用程序:
    --module-path /path/javafx-sdk/lib    --add-modules=javafx.controls,javafx.fxml

在 Windows 上,相同的选项如下所示:

--module-path C:\path\javafx-sdk\lib  
--add-modules=javafx.controls,javafx.fxml

/path/JavaFX/C:\path\JavaFX\ 是您需要用实际路径替换的占位符包含 JavaFX SDK 的文件夹。

假设应用程序的主类是HelloWorld,对于IntelliJ,在VM options字段中输入前面的选项,如如下(示例适用于 Linux):

JDK17 |java17学习 第 11 章 网络编程

这些选项必须添加到 HelloWorldRun/Debug Configurations, BlendEffect ,以及源代码ch12_gui包的OtherEffects类。如果您更喜欢不同的 IDE 或有不同的操作系统,您可以在 openjfx.io 中找到 关于如何设置的建议文档(https://openjfx.io/openjfx-docs)。

从命令行运行 HelloWorldBlendEffectOtherEffects 类,在项目根目录( pom.xml 文件所在位置)的Linux平台上使用以下命令:

mvn clean package
java --module-path /path/javafx-sdk/lib                 \
     --add-modules=javafx.controls,javafx.fxml          \
     -cp target/examples-1.0-SNAPSHOT.jar:target/libs/* \
      com.packt.learnjava.ch12_gui.HelloWorld
java --module-path /path/javafx-sdk/lib                  \
     --add-modules=javafx.controls,javafx.fxml           \
     -cp target/examples-1.0-SNAPSHOT.jar:target/libs/*  \ 
      com.packt.learnjava.ch12_gui.BlendEffect
java --module-path /path/javafx-sdk/lib                  \
     --add-modules=javafx.controls,javafx.fxml           \
     -cp target/examples-1.0-SNAPSHOT.jar:target/libs/*  \ 
      com.packt.learnjava.ch12_gui.OtherEffects

在 Windows 上, 相同的命令如下所示:

mvn clean package
java --module-path C:\path\javafx-sdk\lib                \
     --add-modules=javafx.controls,javafx.fxml           \
     -cp target\examples-1.0-SNAPSHOT.jar;target\libs\*  \
      com.packt.learnjava.ch12_gui.HelloWorld
java --module-path C:\path\javafx-sdk\lib                 \
     --add-modules=javafx.controls,javafx.fxml            \
     -cp target\examples-1.0-SNAPSHOT.jar;target\libs\*   \
      com.packt.learnjava.ch12_gui.BlendEffect
java --module-path C:\path\javafx-sdk\lib                  \
     --add-modules=javafx.controls,javafx.fxml             \
     -cp target\examples-1.0-SNAPSHOT.jar;target\libs\*    \
      com.packt.learnjava.ch12_gui.OtherEffects

每个 HelloWorldBlendEffectOtherEffects 类都有两个 start() 方法:start1()start2()。运行一次类后,将 start() 重命名为 start1(),并将 start1 ()start(),然后再次运行上述命令。然后,将 start() 重命名为 start2(),并将 start2() > 作为 start(),然后再次运行前面的命令。以此类推,直到所有 start() 方法都执行完毕。这样,您将看到本章中所有示例的结果。

JavaFX 的高级介绍到此结束。有了这个,我们进入最令人兴奋的(对于任何程序员)部分:编写代码。

HelloWorld with JavaFX

这是 HelloWorld JavaFX 应用程序,它显示 Hello, World!退出 文字:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class HelloWorld extends Application {
  public static void main(String... args) {
      launch(args);
  }
  @Override
  public void start(Stage primaryStage) {
    Text txt = new Text("Hello, world!");
    txt.relocate(135, 40);
    Button btn = new Button("Exit");
    btn.relocate(155, 80);
    btn.setOnAction(e:> {
        System.out.println("Bye! See you later!");
        Platform.exit();
    });
    Pane pane = new Pane();
    pane.getChildren().addAll(txt, btn);
    primaryStage
        .setTitle("The primary stage (top-level container)");
    primaryStage.onCloseRequestProperty()
        .setValue(e:> System.out.println(
                                       "Bye! See you later!"));
    primaryStage.setScene(new Scene(pane, 350, 150));
    primaryStage.show();
  }
}

如您所见,应用程序是通过调用 Application.launch(String... args) 静态方法启动的。 start(Stage primaryStage) 方法创建一个 Text 节点,其中包含消息 Hello, World! 位于绝对位置 135(水平)和 40(垂直)。然后,它 创建另一个节点 Button,其中文本 Exit 位于在绝对位置 155(水平)和 80(垂直)。分配给 Button 的动作(当它被点击时),打印出 Bye!稍后再见! 在屏幕上并使用 Platform.exit() 方法强制应用程序退出。这两个节点作为子节点添加到布局窗格中,允许绝对定位。

Stage 对象的标题为初级阶段(顶级容器)。在单击窗口右上角的关闭窗口符号(x 按钮)时,还会为其分配一个操作:Linux 系统的左上角和 Windows 的右上角系统。

在创建操作时,我们使用了 Lambda 表达式,我们将在 第 13 章函数式编程

创建的布局窗格设置在 Scene 对象上。场景大小设置为水平 350 像素和垂直 150 像素。 Scene 对象被放置在舞台上。然后,通过调用 show() 方法显示舞台。

如果我们运行前面的应用程序(HellowWorld类的start()方法),会弹出如下窗口:

JDK17 |java17学习 第 11 章 网络编程

单击 Exit 按钮会显示预期的消息:

JDK17 |java17学习 第 11 章 网络编程

但是,如果您需要在单击 x 按钮并关闭窗口后执行其他操作,则可以添加 stop() 的实现HelloWorld 类的 方法。在这个例子中,它看起来如下:

@Override
public void stop(){
    System.out.println(
                  "Doing what has to be done before closing");}

如果单击 x 按钮或 Exit 按钮,显示屏将显示以下内容:

JDK17 |java17学习 第 11 章 网络编程

此示例让您了解 JavaFX 的工作原理。从现在开始,在回顾 JavaFX 功能时,我们将只展示 start() 方法中的代码。

该工具包有大量的包,每个包都有很多类,每个类都有很多方法。我们将无法讨论所有这些。相反,我们将以最简单直接的方式呈现 JavaFX 功能的所有主要领域的概述。

Control elements

控制元素 包含在 javafx.scene.control 包中( https://openjfx.io/javadoc/11 /javafx.controls/javafx/scene/control/package-summary.html)。其中有 80 多个,包括按钮、文本字段、复选框、标签、菜单、进度条和滚动条等等。正如我们已经提到的,每个控件元素都是 Node 的子类,它有 200 多个方法。因此,您可以想象使用 JavaFX 构建的 GUI 可以有多么丰富和微调。然而,本书的范围仅允许我们涵盖少数元素及其方法。

我们已经在上一节的示例中实现了一个按钮。现在让我们使用标签和文本字段来创建一个简单的表单,其中包含输入字段(名字、姓氏和年龄)和 Submit 按钮。我们将逐步构建它。以下所有代码片段都是 HelloWorld 类中另一个 start() 方法的连续部分(重命名之前的 start() 方法 start1(),并将 start2() 方法重命名为 start())。

首先,让我们创建控件:

Text txt = new Text("Fill the form and click Submit");
TextField tfFirstName = new TextField();
TextField tfLastName = new TextField();
TextField tfAge = new TextField();
Button btn = new Button("Submit");
btn.setOnAction(e:> action(tfFirstName, tfLastName, tfAge));

您可以猜到,文本将用作表单说明。其余部分非常简单,看起来与我们在 HelloWorld 示例中看到的非常相似。 action() 是一个实现为以下方法的函数:

void action(TextField tfFirstName, 
                TextField tfLastName, TextField tfAge ) {
    String fn = tfFirstName.getText();
    String ln = tfLastName.getText();
    String age = tfAge.getText();
    int a = 42;
    try {
        a = Integer.parseInt(age);
    } catch (Exception ex){}
    fn = fn.isBlank() ? "Nick" : fn;
    ln = ln.isBlank() ? "Samoylov" : ln;
    System.out.println("Hello, "+fn+" "+ln + ", age " + 
                                                      a + "!");
    Platform.exit();
}

此函数接受三个参数(javafx.scene.control.TextField 对象),然后获取提交的输入值并仅打印他们。该代码确保始终有一些默认值可用于打印,并且输入 age 的非数字值不会破坏应用程序。

控制和操作到位后,我们使用 javafx.scene.layout.GridPane 类将它们放入网格布局中:

GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(15);
grid.setVgap(5);
grid.setPadding(new Insets(20, 20, 20, 20));

GridPane 布局窗格的行和列构成了可以在其中设置节点的单元格。节点可以跨越列和行。 setAlignment() 方法将网格的位置设置为场景的中心(默认位置是场景的左上角)。 setHgap()setVgap() 方法设置列(水平)和行(垂直)之间的间距(以像素为单位) )。 setPadding() 方法沿网格窗格的边界添加了一些空间。 Insets() 对象按顶部、右侧、底部和左侧的顺序设置值(以像素为单位)。

现在,我们要将创建的节点放置在相应的单元格中(分为两列):

int i = 0;
grid.add(txt,    1, i++, 2, 1);
GridPane.setHalignment(txt, HPos.CENTER);
grid.addRow(i++, new Label("First Name"), tfFirstName);
grid.addRow(i++, new Label("Last Name"),  tfLastName);
grid.addRow(i++, new Label("Age"), tfAge);
grid.add(btn,    1, i);
GridPane.setHalignment(btn, HPos.CENTER);

add() 方法接受 三个或五个参数:

  • 节点、列索引和行索引
  • 节点、列索引、行索引、要跨越多少列、要跨越多少行

列和行索引从 0 开始。

setHalignment() 方法设置节点在单元格中的位置。 HPos 枚举具有值 LEFTRIGHTCENTERaddRow(int i, Node... nodes) 方法接受行索引和节点的可变参数。我们用它来放置 LabelTextField 对象。

start() 方法的其余部分与 HelloWorld 示例非常相似(仅更改了标题和大小):

primaryStage.setTitle("Simple form example");
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.setScene(new Scene(grid, 300, 200));
primaryStage.show();

如果我们运行新实现的 start() 方法,结果如下:

JDK17 |java17学习 第 11 章 网络编程

 

我们可以如下填写数据,例如:

JDK17 |java17学习 第 11 章 网络编程

单击提交按钮后,将显示以下消息并退出应用程序:

JDK17 |java17学习 第 11 章 网络编程

为了帮助可视化布局,尤其是在设计更复杂的情况下,您可以使用 setGridLinesVisible(boolean v) 网格方法使网格线可见。它有助于查看单元格是如何对齐的。我们可以在示例中添加(取消注释)以下行:

grid.setGridLinesVisible(true);

我们再次运行,结果如下:

JDK17 |java17学习 第 11 章 网络编程

如您所见,布局现在被明确地勾勒出来,这有助于我们将设计可视化。

javafx.scene.layout 包包含 Pane 等 24 个布局类(我们在 HelloWorld 示例),StackPane(允许我们覆盖节点),FlowPane(允许节点的位置随着窗口大小的变化而流动)和 AnchorPane (保留 节点相对于其锚点的位置),仅举几例。 VBox 布局将在下一节中演示,Charts

Charts

JavaFX 在 javafx.scene.chart 包中提供了 以下图表组件用于数据可视化:

  • LineChart:在系列中的数据点之间添加 一条线。通常用于呈现一段时间内的趋势。
  • AreaChart:类似于LineChart,但填充连接数据点和轴的线之间的区域。通常用于比较一段时间内的累计总数。
  • BarChart:将数据呈现为矩形条。用于离散数据的可视化。
  • PieChart:呈现一个分成多个段(用不同颜色填充)的圆,每个段代表一个值占总数的比例。我们将在本节中演示它。
  • BubbleChart:将数据呈现为称为气泡的二维椭圆形,允许呈现三个参数。
  • ScatterChart:按原样呈现一系列数据点。有助于识别是否存在聚类(数据相关性)。

以下示例(HellowWorld 类的 start3() 方法)演示了如何将测试结果显示为饼图图表。每个段代表测试成功、失败或忽略的数量

Text txt = new Text("Test results:");
PieChart pc = new PieChart();
pc.getData().add(new PieChart.Data("Succeed", 143));
pc.getData().add(new PieChart.Data("Failed" ,  12));
pc.getData().add(new PieChart.Data("Ignored",  18));
VBox vb = new VBox(txt, pc);
vb.setAlignment(Pos.CENTER);
vb.setPadding(new Insets(10, 10, 10, 10));
primaryStage.setTitle("A chart example");
primaryStage.onCloseRequestProperty()
      .setValue(e:> System.out.println("Bye! See you later!"));
primaryStage.setScene(new Scene(vb, 300, 300));
primaryStage.show();

我们创建了两个节点——TextPieChart——并将它们放置在 的单元格中VBox 布局,将它们设置在一列中,一个在另一个之上。我们在 VBox 窗格的边缘添加了 10 个像素的填充。请注意,VBox 扩展了 NodePane 类,就像其他窗格一样。我们还使用 setAlignment() 方法将窗格定位在场景的中心。除了场景标题和大小之外,其余部分与之前的所有其他示例相同。

如果我们运行这个例子(重命名之前的 start() 方法 start2(),并重命名 start3() 方法 start()),结果如下:

JDK17 |java17学习 第 11 章 网络编程

 

PieChart 类以及任何其他图表具有其他几种方法,可用于呈现 更复杂和动态的数据以用户友好的 方式。

现在,让我们讨论如何使用 Cascading Style Sheets (CSS) 的强大功能来丰富应用程序的外观和感觉.

Applying CSS

默认情况下,JavaFX 使用分发 JAR 文件附带的样式表。要覆盖默认样式,您可以使用 getStylesheets() 方法向场景添加样式表:

scene.getStylesheets().add("/mystyle.css");

mystyle.css 文件必须放在 src/main/resources 文件夹中。让我们这样做,将具有以下内容的 mystyle.css 文件添加到 HelloWorld 示例中:

#text-hello {
  :fx-font-size: 20px;
   -fx-font-family: "Arial";
   -fx-fill: red;
}
.button {
   -fx-text-fill: white;
   -fx-background-color: slateblue;
}

如您所见,我们希望设置 Button 节点和具有 Text 节点的样式">text-hello 某种方式的 ID。我们还必须修改 HelloWorld 示例,将 ID 添加到 Text 元素和样式表文件到场景中(start4() 方法):

Text txt = new Text("Hello, world!");
txt.setId("text-hello");
txt.relocate(115, 40);
Button btn = new Button("Exit");
btn.relocate(155, 80);
btn.setOnAction(e -> {
    System.out.println("Bye! See you later!");
    Platform.exit();
});
Pane pane = new Pane();
pane.getChildren().addAll(txt, btn);
Scene scene = new Scene(pane, 350, 150);
scene.getStylesheets().add("/mystyle.css");
primaryStage.setTitle("The primary stage (top-level container)");
primaryStage.onCloseRequestProperty()
   .setValue(e -> System.out.println("\nBye! See you later!"));
primaryStage.setScene(scene);
primaryStage.show();

如果我们运行这个 代码(重命名之前的 start() 方法 start3() 并将 start4() 方法重命名为 start()),结果如下:

JDK17 |java17学习 第 11 章 网络编程

或者,可以在将用于覆盖文件样式表的任何节点上设置内联样式,无论是否默认。让我们在 HelloWorld 示例的最新版本中添加(取消注释)以下行:

btn.setStyle("-fx-text-fill: white; -fx-background-color: red;");

如果我们再次运行该示例,结果将如下所示:

JDK17 |java17学习 第 11 章 网络编程

查看 JavaFX CSS 参考指南(https:// /docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html)了解自定义样式的多样性和可能的​​选项。

现在,让我们讨论另一种为 FX 应用程序构建用户界面的方法,无需编写 Java 代码,使用 FX 标记语言(FXML)。

Using FXML

FXML 是一种 基于 XML 的语言,它允许构建用户界面并独立于应用程序(业务)逻辑(如就外观而言,或其他与演示相关的更改)。使用 FXML,您甚至无需编写一行 Java 代码即可设计用户界面。

FXML 没有模式,但它的功能反映了用于构建场景的 JavaFX 对象的 API。这意味着您可以使用 API 文档来了解 FXML 结构中允许使用哪些标签和属性。大多数时候,JavaFX 类可以用作标记,它们的属性可以用作属性。

除了 FXML 文件(视图)之外,控制器(Java 类)还可用于处理模型和组织页面流。模型由视图和控制器管理的域对象组成。它还允许使用 CSS 样式和 JavaScript 的所有功能。但是,在本书中,我们将只能演示基本的 FXML 功能。其余的可以在 FXML 介绍中找到 (https ://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html)和许多在线可用的好教程。

为了演示 FXML 的使用,我们将重现我们在 Control elements 部分中创建的简单表单,然后通过添加页面流来增强它。以下是我们的表单(包含名字、姓氏和年龄)如何在 FXML 中表示:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.Scene?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.TextField?>
<Scene fx:controller="com.packt.learnjava.ch12_gui.HelloWorldController"
       xmlns:fx="http://javafx.com/fxml"
       width="350" height="200">
    <GridPane alignment="center" hgap="15" vgap="5">
        <padding>
            <Insets top="20" right="20" bottom="20" left="20"/>
        </padding>
        <Text id="textFill" text="Fill the form and click 
         Submit" GridPane.rowIndex="0" GridPane.columnSpan="2">
            <GridPane.halignment>center</GridPane.halignment>
        </Text>
        <Label text="First name"
               GridPane.columnIndex="0" GridPane.rowIndex="1"/>
        <TextField fx:id="tfFirstName"
               GridPane.columnIndex="1" GridPane.rowIndex="1"/>
        <Label text="Last name"
               GridPane.columnIndex="0" GridPane.rowIndex="2"/>
        <TextField fx:id="tfLastName"
               GridPane.columnIndex="1" GridPane.rowIndex="2"/>
        <Label text="Age"
               GridPane.columnIndex="0" GridPane.rowIndex="3"/>
        <TextField fx:id="tfAge"
               GridPane.columnIndex="1" GridPane.rowIndex="3"/>
        <Button text="Submit"
                GridPane.columnIndex="1" GridPane.rowIndex="4"
                onAction="#submitClicked">
            <GridPane.halignment>center</GridPane.halignment>
        </Button>
    </GridPane>
</Scene>

如您所见,它 表达了您已经熟悉的所需场景结构,并指定了控制器类HelloWorldController,它我们很快就会看到。正如我们已经提到的,这些标签与我们一直使用的类名相匹配,以仅使用 Java 构建相同的 GUI。我们将前面的 FXML 代码(作为 helloWorld.fxml 文件)放入 resources 文件夹。

现在,我们来看看 start5() 方法(重命名为 start()) >HelloWorld 类使用 helloWorld.fxml 文件:

try {
  ClassLoader classLoader =
             Thread.currentThread().getContextClassLoader();
  String file =
        classLoader.getResource("helloWorld.fxml").getFile();
  FXMLLoader lder = new FXMLLoader();
  lder.setLocation(new URL("file:" + file));
  Scene scene = lder.load();
  primaryStage.setTitle("Simple form example");
  primaryStage.setScene(scene);
  primaryStage.onCloseRequestProperty().setValue(e ->
                  System.out.println("\nBye! See you later!"));
  primaryStage.show();
} catch (Exception ex){
    ex.printStackTrace();
}

start() 方法 只是加载 helloWorld.fxml 文件并设置阶段,后者与我们之前的示例完全相同。

现在,让我们看看 HelloWorldController 类。如果需要,我们可以启动只有以下内容的应用程序:

public class HelloWorldController {
    @FXML
    protected void submitClicked(ActionEvent e) {
    }
}

表单将被显示,但按钮单击不会执行任何操作。这就是我们在谈论独立于应用程序逻辑的用户界面开发时的意思。注意 @FXML 注释。它使用它们的 ID 将方法和属性绑定到 FXML 标签。以下是完整控制器实现的外观:

@FXML
private TextField tfFirstName;
@FXML
private TextField tfLastName;
@FXML
private TextField tfAge;
@FXML
protected void submitClicked(ActionEvent e) {
    String fn = tfFirstName.getText();
    String ln = tfLastName.getText();
    String age = tfAge.getText();
    int a = 42;
    try {
        a = Integer.parseInt(age);
    } catch (Exception ex) {
    }
    fn = fn.isBlank() ? "Nick" : fn;
    ln = ln.isBlank() ? "Samoylov" : ln;
    String hello = "Hello, " + fn + " " + ln + ", age " + 
                                                       a + "!";
    System.out.println(hello);
    Platform.exit();
}

在大多数情况下,它对您来说应该非常熟悉。唯一的区别是我们不是直接引用字段及其值(如前所述),而是使用标有 @FXML 注释的绑定。如果我们现在运行 HelloWorld 类(不要忘记将 start5() 方法重命名为 start()),页面外观和行为将与我们在控制元素部分中描述的完全相同:

JDK17 |java17学习 第 11 章 网络编程

如果点击右上角的x按钮,屏幕上会出现以下输出:

JDK17 |java17学习 第 11 章 网络编程

如果单击 Submit 按钮,输出将显示以下消息:

JDK17 |java17学习 第 11 章 网络编程

现在,让我们看看在 gui 文件夹中作为单独项目实现的两个页面的独立 GUI 应用程序:

JDK17 |java17学习 第 11 章 网络编程

如您所见,此应用程序由主要的 GuiApp 类、两个 Controller 类、User 类和两个页面(.fxml 文件)。让我们从 .fxml 文件开始。为简单起见,page01.fxml文件的内容与前面描述的helloWorld.fxml文件的内容几乎完全相同部分。唯一的区别是它引用了 Controller01 类,该类的 start() 方法与 start5() 方法也是前面描述的。 GuiApp 类看起来很简单:

public class GuiApp extends Application {
    public static void main(String... args) {
        launch(args);
    }
    @Override
    public void stop(){
        System.out.println("Doing what has to be done...");
    }
    public void start(Stage primaryStage) {
        Controller01.start(primaryStage);
    }
}

如您所见,它只是调用了 Controller01 类中的 start() 方法,从而将熟悉的页面显示为你的表格:

JDK17 |java17学习 第 11 章 网络编程

填写完表单并点击Submit按钮后,提交的值会在Controller01类中处理,然后传递给Controller02 类,使用 submitClicked() 方法 ="literal">Controller01 类:

    @FXML
    protected void submitClicked(ActionEvent e) {
        String fn = tfFirstName.getText();
        String ln = tfLastName.getText();
        String age = tfAge.getText();
        int a = 42;
        try {
            a = Integer.parseInt(age);
        } catch (Exception ex) {
        }
        fn = fn.isBlank() ? "Nick" : fn;
        ln = ln.isBlank() ? "Samoylov" : ln;
        Controller02.goToPage2(new User(a, fn, ln));
 
        Node source = (Node) e.getSource();
        Stage stage = (Stage) source.getScene().getWindow();
        stage.close();
    }

Controller02.goToPage2() 方法 如下所示:

public static void goToPage2(User user) {
  try {
    ClassLoader classLoader =
             Thread.currentThread().getContextClassLoader();
    String file = classLoader.getResource("fxml" + 
                  File.separator + "page02.fxml").getFile();
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(new URL("file:" + file));
    Scene scene = loader.load();
 
    Controller02 c = loader.getController();
    String hello = "Hello, " + user.getFirstName() + " " + 
        user.getLastName() + ", age " + user.getAge() + "!";
    c.textHello.setText(hello);
 
    Stage primaryStage = new Stage();
    primaryStage.setTitle("Second page of GUI App");
    primaryStage.setScene(scene);
    primaryStage.onCloseRequestProperty()
        .setValue(e -> {
                          System.out.println("\nBye!");
                          Platform.exit();
                       });
    primaryStage.show();
  } catch (Exception ex) {
       ex.printStackTrace();
  }
}

第二页只显示接收到的数据。以下是其 FXML 的外观(page2.fxml 文件):

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.Scene?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.layout.GridPane?>
<Scene fx:controller="com.packt.lernjava.gui.Controller02"
       xmlns:fx="http://javafx.com/fxml"
       width="350" height="150">
    <GridPane alignment="center" hgap="15" vgap="5">
        <padding>
            <Insets top="20" right="20" bottom="20" left="20"/>
        </padding>
        <Text fx:id="textHello"
              GridPane.rowIndex="0" GridPane.columnSpan="2">
            <GridPane.halignment>center</GridPane.halignment>
        </Text>
        <Text id="textDo" text="Do what has to be done here"
              GridPane.rowIndex="1" GridPane.columnSpan="2">
            <GridPane.halignment>center</GridPane.halignment>
        </Text>
    </GridPane>
</Scene>

可以看到,页面只有两个只读的Text字段。第一个(带有 id="textHello")显示从前一页传递的数据。第二个只是显示消息,Do what has to be done here。这不是很复杂,但它演示了如何组织数据流和页面。

如果我们执行 GuiApp 类,我们会看到熟悉的表格并可以用数据填充它:

JDK17 |java17学习 第 11 章 网络编程

当我们点击Submit按钮后,这个窗口会关闭,新的窗口会出现:

JDK17 |java17学习 第 11 章 网络编程

现在,我们可以单击左上角(或 Windows 右上角)的 x 按钮并看到以下消息:

JDK17 |java17学习 第 11 章 网络编程

stop() 方法按预期工作。

至此,我们结束对 FXML 的介绍,并转到下一个主题,即向 JavaFX 应用程序添加 HTML。

Embedding HTML

将 HTML 添加到 JavaFX 很容易。您所要做的就是使用 javafx.scene.web.WebView 类,它提供了一个窗口,添加的 HTML 在其中呈现类似于它在浏览器中发生的方式。 WebView 类使用开源浏览器引擎 WebKit,因此支持完整的浏览功能。

与所有其他 JavaFX 组件一样,WebView 类扩展了 Node 类,并且可以在 Java 代码中这样处理。此外,它有自己的属性和方法,允许通过设置窗口大小(最大、最小和首选高度和宽度)、字体比例、缩放率、添加 CSS、启用上下文(右键单击)菜单等。 getEngine() 方法返回一个与之关联的 javafx.scene.web.WebEngine 对象。它提供了加载 HTML 页面、导航它们、对加载的页面应用不同样式、访问它们的浏览历史和文档模型以及执行 JavaScript 的能力。

要开始使用 javafx.scene.web 包,首先必须执行两个步骤:

  1. 将以下依赖项添加到 pom.xml 文件中:
    <依赖>    <groupId>org.openjfx</groupId>    <artifactId>javafx-web</artifactId>    <版本>11.0.2</版本> </dependency>

javafx-web 的版本通常与 Java 版本保持同步,但在撰写本文时,javafx-web 尚未发布,因此我们正在使用最新的可用版本,11.0.2

  1. 由于 javafx-web 使用 com.sun.* 包,它已从 Java 9 中删除(https://docs.oracle.com/javase/9​​/migrate/toc.htm#JSMIG-GUID-F7696E02-A1FB-4D5A-B1F2-89E7007D4096),访问 com.sun.* 来自 Java 9+ 的包,除了 --module-path--add-modules,在 HtmlWebView 类的 Run/Debug Configuration 中的 JavaFX 基础部分中进行了描述 ch12_gui 包(对于 Windows,将斜杠符号更改为反斜杠):
    --add-exports javafx.graphics/com.sun.javafx.sg.prism=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.util=ALL-UNNAMED  --add-exports javafx.base/com.sun.javafx.logging=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.prism=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.glass.ui=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.geom.transform=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.tk=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.glass.utils=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.font=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.application=ALL-UNNAMED  --add-exports javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.scene.input=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.prism.paint=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.scenario.effect=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.text=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.iio=ALL-UNNAMED --add-exports javafx.graphics/com.sun.scenario.effect.impl.prism=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.scene.text=ALL-UNNAMED
  2. 要从命令行执行 HtmlWebView 类,请转到 examples 文件夹并在 Unix/Linux/macOS 上使用以下命令系统(不要忘记将 /path/JavaFX 替换为 包含 JavaFX SDK 的文件夹的实际路径):
    mvn clean package java --module-path /path/javaFX/lib --add-modules=javafx.controls,javafx.fxml --add-exports javafx.graphics/com.sun.javafx.sg。 prism=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.util=ALL-UNNAMED --add-exports javafx.base/com.sun.javafx.logging=ALL-UNNAMED --add-exports javafx.graphics/com.sun.prism=ALL-UNNAMED --add-exports javafx.graphics/com.sun.glass.ui= ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.geom.transform=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.tk=ALL-UNNAMED --add-exports javafx.graphics/com.sun.glass.utils=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.javafx.font=ALL-UNNAMED  --add-exports javafx.graphics/ com.sun.javafx.application=ALL-UNNAMED --add-exports javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.scene。输入=全部未命名 --add-exports javafx.graphics/com.sun.javafx.ge om=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.prism.paint=ALL-UNNAMED  --add-exports javafx.graphics/com.sun.scenario.effect=ALL-未命名 --add-exports javafx.graphics/com.sun.javafx.text=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.iio=ALL-UNNAMED --add-exports javafx.graphics/ com.sun.scenario.effect.impl.prism=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.scene.text=ALL-UNNAMED  ; -cp target/examples-1.0-SNAPSHOT.jar com.packt.learnjava.ch12_gui.HtmlWebView
  3. 在 Windows 上,相同的 命令如下所示(不要忘记将 C:\path\JavaFX 替换为实际路径到包含 JavaFX SDK 的文件夹):
    mvn clean package java --module-path C:\path\JavaFX\lib --add-modules=javafx.controls,javafx.fxml --add-exports javafx.graphics\com.sun.javafx。 sg.prism=ALL-UNNAMED --add-exports javafx.graphics\com.sun.javafx.scene=ALL-UNNAMED --add-exports javafx.graphics\com.sun.javafx.util=ALL-UNNAMED --add -exports javafx.base\com.sun.javafx=ALL-UNNAMED --add-exports javafx.base\com.sun.javafx.logging=ALL-UNNAMED --add-exports javafx.graphics\com.sun.prism= ALL-UNNAMED --add-exports javafx.graphics\com.sun.glass.ui=ALL-UNNAMED --add-exports javafx.graphics\com.sun.javafx.geom.transform=ALL-UNNAMED --add-exports javafx.graphics\com.sun.javafx.tk=ALL-UNNAMED --add-exports javafx.graphics\com.sun.glass.utils=ALL-UNNAMED  --add-exports javafx.graphics\com.sun .javafx.font=ALL-UNNAMED  --add-exports javafx.graphics\com.sun.javafx.application=ALL-UNNAMED --add-exports javafx.controls\com.sun.javafx.scene.control= ALL-UNNAMED --add-exports javafx.graphics\com.sun.javafx.scene.input=ALL-未命名 --add-exports javafx.graphics\com.sun.javafx.geom=ALL-UNNAMED  --add-exports javafx.graphics\com.sun.prism.paint=ALL-UNNAMED  --add -exports javafx.graphics\com.sun.scenario.effect=ALL-UNNAMED --add-exports javafx.graphics\com.sun.javafx.text=ALL-UNNAMED --add-exports javafx.graphics\com.sun。 javafx.iio=ALL-UNNAMED --add-exports javafx.graphics\com.sun.scenario.effect.impl.prism=ALL-UNNAMED --add-exports javafx.graphics\com.sun.javafx.scene.text= ALL-UNNAMED  -cp target\examples-1.0-SNAPSHOT.jar com.packt.learnjava.ch12_gui.HtmlWebView

HtmlWebView 类也包含几个 start() 方法。如JavaFX基础部分所述,一一重命名并执行它们

现在让我们看几个例子。我们创建一个新应用程序 HtmlWebView,并使用 VM 选项(--module-path, < code class="literal">--add-modules 和 --add-exports) 我们已经描述过了。现在,我们可以编写和执行使用 WebView 类的代码。

首先,下面是如何将简单的 HTML 添加到 JavaFX 应用程序(HtmlWebView 类中的 start() 方法):

WebView wv = new WebView();
WebEngine we = wv.getEngine();
String html = 
        "<html><center><h2>Hello, world!</h2></center></html>";
we.loadContent(html, "text/html");
Scene scene = new Scene(wv, 200, 60);
primaryStage.setTitle("My HTML page");
primaryStage.setScene(scene);
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.show();

上述代码创建了一个WebView对象,从中获取WebEngine对象,使用获取到的 WebEngine 对象加载 HTML,设置场景中的 WebView 对象, 配置舞台。 loadContent() 方法接受两个字符串:内容及其 mime 类型。内容字符串可以在代码中构造或通过读取 .html 文件创建。

如果我们运行 HtmlWebView 类,结果如下:

JDK17 |java17学习 第 11 章 网络编程

如有必要,您可以在同一窗口中显示其他 JavaFX 节点以及 WebView 对象。例如,让我们在嵌入的 HTML 上方添加一个 Text 节点(start2() 方法">HtmlWebView 类):

Text txt = new Text("Below is the embedded HTML:");
WebView wv = new WebView();
WebEngine we = wv.getEngine();
String html = 
      "<html><center><h2>Hello, world!</h2></center></html>";
we.loadContent(html, "text/html");
VBox vb = new VBox(txt, wv);
vb.setSpacing(10);
vb.setAlignment(Pos.CENTER);
vb.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(vb, 300, 120);
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX with embedded HTML");
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.show();

如您所见, 对象不是直接在场景中设置的,而是在布局对象上设置的,以及txt 对象。然后,在场景中设置布局对象。上述代码的结果如下:

JDK17 |java17学习 第 11 章 网络编程

对于更复杂的 HTML 页面,可以使用 load() 方法直接从文件中加载它。为了演示这种方法,让我们在 resources 文件夹中创建一个 form.html 文件,其内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>The Form</title>
</head>
<body>
<form action="http://someServer:port/formHandler" method="post">
  <table>
    <tr>
      <td><label for="firstName">First name:</label></td>
      <td><input type="text" id="firstName" name="firstName">
      </td>
    </tr>
    <tr>
      <td><label for="lastName">Last name:</label></td>
      <td><input type="text" id="lastName" name="lastName">
      </td>
    </tr>
    <tr>
      <td><label for="age">Age:</label></td>
      <td><input type="text" id="age" name="age"></td>
    </tr>
    <tr>
      <td></td>
      <td align="center">
          <button id="submit" name="submit">Submit</button>
      </td>
    </tr>
  </table>
</form>
</body>
</html>

该 HTML 呈现的表单类似于我们在 使用 FXML 部分中创建的表单。点击 Submit 按钮后,表单数据被发送到服务器的 \formHandler URI(参见 <form> HTML 标签)。要在 JavaFX 应用程序中显示此表单,可以使用 以下代码:

ClassLoader classLoader =
              Thread.currentThread().getContextClassLoader();
String file = classLoader.getResource("form.html").getFile();
Text txt = new Text("Fill the form and click Submit");
WebView wv = new WebView();
WebEngine we = wv.getEngine();
File f = new File(file);
we.load(f.toURI().toString());
VBox vb = new VBox(txt, wv);
vb.setSpacing(10);
vb.setAlignment(Pos.CENTER);
vb.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(vb, 300, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX with embedded HTML");
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.show();

如您所见,与我们其他示例的不同之处在于,我们现在使用 File 类及其 toURI() 方法直接访问 src/main/resources/form.html 文件中的 HTML,无需先将内容转换为字符串。如果您运行 HtmlWebView< 的 start3() 方法(重命名为 start() ) /code> 类, 结果如下所示:

JDK17 |java17学习 第 11 章 网络编程

当您需要从 JavaFX 应用程序发送请求或发布数据时,此解决方案很有用。但是,当您希望用户填写的表单已经在服务器上可用时,您可以从 URL 加载它。

例如,让我们在 JavaFX 应用程序中加入 Google 搜索。我们可以通过将 load() 方法的参数值更改为我们要加载的页面的 URL(start4( ) HtmlWebView 类的方法):

Text txt = new Text("Enjoy searching the Web!");
WebView wv = new WebView();
WebEngine we = wv.getEngine();
we.load("http://www.google.com");
VBox vb = new VBox(txt, wv);
vb.setSpacing(20);
vb.setAlignment(Pos.CENTER);
vb.setStyle("-fx-font-size: 20px;-fx-background-color: lightblue;");
vb.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(vb,750,500);
primaryStage.setScene(scene);
primaryStage.setTitle(
                   "JavaFX with the window to another server");
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.show();

我们还在布局中添加了一个 样式,以增加字体并为背景添加颜色,因此我们可以看到嵌入渲染的 HTML 区域的轮廓。当我们运行这个例子时(别忘了把start4()方法重命名为start()),出现如下窗口:

JDK17 |java17学习 第 11 章 网络编程

在此窗口中,您 可以执行您通常通过浏览器访问的搜索的所有方面。

而且,正如我们已经提到的,您可以放大呈现的页面。例如,如果我们在前面的示例中添加 wv.setZoom(1.5) 行,结果将如下所示:

JDK17 |java17学习 第 11 章 网络编程

同样,我们可以设置字体的比例,甚至可以从文件中设置样式:

wv.setFontScale(1.5);
we.setUserStyleSheetLocation("mystyle.css");

但是请注意,我们在 WebView 对象中设置字体比例,而在 WebEngine 对象中设置样式。

我们还可以使用 WebEngine 类方法 getDocument() 访问(和操作)加载页面的 DOM 对象:

Document document = we.getDocument();

而且,我们可以访问浏览历史,获取当前索引,并前后移动历史:

WebHistory history = we.getHistory();  
int currInd = history.getCurrentIndex(); 
history.go(-1);
history.go( 1);

对于历史的每个条目,我们可以提取其 URL、标题或上次访问日期:

WebHistory history = we.getHistory();
ObservableList<WebHistory.Entry> entries = 
                                          history.getEntries();
for(WebHistory.Entry entry: entries){
    String url = entry.getUrl();
    String title = entry.getTitle();
    Date date = entry.getLastVisitedDate();
}

阅读 WebViewWebEngine 类的文档,以获取有关如何利用它们的功能的更多想法。

Playing media

将图像 添加到 JavaFX 应用程序的 场景不需要 com.sun。 * 包,因此不需要 Embedding HTML 部分中列出的 --add-export VM 选项。但是,无论如何拥有它们并没有什么坏处,所以如果您已经添加了它们,请保留 --add-export 选项。

可以使用 javafx.scene.image.Imagejavafx.scene.image.ImageView 类将图像包含在场景中.为了演示如何做到这一点,我们将使用 Packt 徽标 packt.png,它位于 resources 文件夹中。这是执行此操作的代码(HelloWorld 类的 start6() 方法):

ClassLoader classLoader =
             Thread.currentThread().getContextClassLoader();
String file = classLoader.getResource("packt.png").getFile();
Text txt = new Text("What a beautiful image!");
FileInputStream input = new FileInputStream(file);
Image image = new Image(input);
ImageView iv = new ImageView(image);
VBox vb = new VBox(txt, iv);
vb.setSpacing(20);
vb.setAlignment(Pos.CENTER);
vb.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(vb, 300, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX with embedded HTML");
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.show();

如果我们运行上面的代码,结果如下:

JDK17 |java17学习 第 11 章 网络编程

当前支持的图像格式为 BMP、GIF、JPEG 和 PNG。查看 ImageImageView 类的 API (https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/image/package-summary。 html) 来了解可以根据需要对图像进行格式化和调整的多种方式。

现在,让我们看看 如何 在 JavaFX 应用程序中使用其他媒体文件。播放音频或电影文件需要 嵌入 HTML 部分中列出的 --add-export VM 选项。

目前支持的编码如下:

  • AAC高级音频编码音频压缩
  • H.264/AVC: H.264/MPEG-4 Part 10 / AVC (高级视频编码) 视频压缩
  • MP3:原始 MPEG-1、2 和 2.5 音频;第一层、第二层和第三层
  • PCM:未压缩的原始音频样本

您可以在 API 文档(https://openjfx.io/javadoc/11/javafx.media/javafx/scene/media/package-summary.html)。

以下三个类允许构建可以添加到场景的媒体播放器:

javafx.scene.media.Media;
javafx.scene.media.MediaPlayer;
javafx.scene.media.MediaView;

Media 类表示媒体的来源。 MediaPlayer 类提供了所有控制媒体播放的方法:play(), stop ()pause()setVolume() 等。您还可以指定媒体应播放的次数。 MediaView 类扩展了 Node 类并且可以添加到场景中。它提供媒体播放器正在播放的媒体的视图,并负责媒体外观。

对于演示,让我们运行start5()方法code class="literal">HtmlWebView 类,播放 resources 文件夹中的 jb.mp3 文件:

Text txt1 = new Text("What a beautiful music!");
Text txt2 = 
   new Text("If you don't hear music, turn up the volume.");
ClassLoader classLoader =
             Thread.currentThread().getContextClassLoader();
String file = classLoader.getResource("jb.mp3").getFile();
File f = new File(file);
Media m = new Media(f.toURI().toString());
MediaPlayer mp = new MediaPlayer(m);
MediaView mv = new MediaView(mp);
VBox vb = new VBox(txt1, txt2, mv);
vb.setSpacing(20);
vb.setAlignment(Pos.CENTER);
vb.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(vb, 350, 100);
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX with embedded media player");
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.show();
mp.play();

请注意 Media 对象 是如何基于源文件构造的。 MediaPlayer 对象是基于 Media 对象构造的,然后设置为 的属性MediaView 类构造函数。 MediaView 对象与两个 Text 对象一起设置在场景中。我们使用 VBox 对象来提供布局。最后,在场景设置在舞台上并且舞台变得可见之后(在 show() 方法完成后),play()< /code> 方法在 MediaPlayer 对象上调用。默认情况下,媒体播放一次。

如果我们执行这段代码,会出现下面的窗口并播放jb.m3文件:

JDK17 |java17学习 第 11 章 网络编程

我们可以添加控件来停止、暂停和调整音量,但这需要更多的代码,这超出了本书的范围。您可以在 Oracle 在线文档 (https ://docs.oracle.com/javafx/2/media/jfxpub-media.htm)。

sea.mp4 电影 文件 可以类似地播放(HtmlWebView 类的 class="literal">start6() 方法):

Text txt = new Text("What a beautiful movie!");
ClassLoader classLoader =
             Thread.currentThread().getContextClassLoader();
String file = classLoader.getResource("sea.mp4").getFile(); 
File f = new File(file);
Media m = new Media(f.toURI().toString());
MediaPlayer mp = new MediaPlayer(m);
MediaView mv = new MediaView(mp);
VBox vb = new VBox(txt, mv);
vb.setSpacing(20);
vb.setAlignment(Pos.CENTER);
vb.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(vb, 650, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX with embedded media player");
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.show();
mp.play();

唯一的区别是显示此特定剪辑的全帧所需的场景大小不同。经过几次试错调整,我们找到了必要的尺寸。或者,我们可以使用 MediaView 方法(autosize()preserveRatioProperty() , setFitHeight(), setFitWidth(), fitWidthProperty() , fitHeightProperty() 和类似的)来调整嵌入窗口的大小并自动匹配场景的大小。如果我们执行前面的例子,会弹出如下窗口,播放剪辑:

JDK17 |java17学习 第 11 章 网络编程

我们甚至可以同时并行播放音频和视频文件,从而为电影提供配乐(HtmlWebView 的 start7() 方法 类):

Text txt1 = new Text("What a beautiful movie and sound!");
Text txt2 = new Text("If you don't hear music, turn up the volume.");
ClassLoader classLoader =
             Thread.currentThread().getContextClassLoader();
String file = classLoader.getResource("jb.mp3").getFile(); 
File fs = new File(file);
Media ms = new Media(fs.toURI().toString());
MediaPlayer mps = new MediaPlayer(ms);
MediaView mvs = new MediaView(mps);
File fv = 
     new File(classLoader.getResource("sea.mp4").getFile());
Media mv = new Media(fv.toURI().toString());
MediaPlayer mpv = new MediaPlayer(mv);
MediaView mvv = new MediaView(mpv);
VBox vb = new VBox(txt1, txt2, mvs, mvv);
vb.setSpacing(20);
vb.setAlignment(Pos.CENTER);
vb.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(vb, 650, 500);
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX with embedded media player");
primaryStage.onCloseRequestProperty()
     .setValue(e -> System.out.println("Bye! See you later!"));
primaryStage.show();
mpv.play();
mps.play();

这样做是可能的,因为每个玩家都由自己的线程执行。

有关 javafx.scene.media 包的更多信息,请在线阅读 API 和开发人员指南,此处提供链接:

Adding effects

javafx.scene.effects 包包含 许多允许向节点添加各种效果的类:

  • Blend:使用预定义的 BlendModes 之一组合来自两个来源(通常是图像)的像素
  • Bloom:使输入图像更亮,使其看起来发光
  • BoxBlur:为图像添加模糊
  • ColorAdjust:允许调整图像的色调、饱和度、亮度和对比度
  • ColorInput:渲染一个填充了给定颜料的矩形区域
  • DisplacementMap:将每个像素移动指定距离
  • DropShadow:在内容后面渲染给定内容的阴影
  • GaussianBlur:使用特定(高斯)方法添加模糊
  • 发光:使输入图像看起来发光
  • InnerShadow:在框架内创建阴影
  • Lighting:模拟光源照射在内容上,使平面物体看起来更逼真
  • MotionBlur:模拟运动中看到的给定内容
  • PerspectiveTransform:转换透视图中的内容
  • Reflection:在实际输入内容下方呈现输入的反射版本
  • SepiaTone:产生棕褐色调效果,类似于古董照片的外观
  • Shadow:创建带有模糊边缘的内容的单色副本

所有效果共享父类,即Effect抽象类。 Node 类有 setEffect(Effect e) 方法,这意味着可以将任何效果添加到任何节点。这是将效果应用于节点的主要方式——在舞台上产生场景的演员(如果我们回想一下本章开头介绍的类比)。

唯一的例外是 Blend 效果,这使得它的使用比其他效果的使用更复杂。除了使用 setEffect(Effect e) 方法,一些 Node 类的子类也有 setBlendMode(BlendMode bm) 方法,允许调节图像在重叠时如何相互融合。因此,可以以不同的方式设置不同的混合效果,这些效果相互覆盖并产生可能难以调试的意外结果。这就是使 Blend 效果使用更加复杂的原因,这就是为什么我们要开始概述 Blend可以使用效果。

三个方面调节两个图像重叠区域的外观(我们在示例中使用两个图像以使其更简单,但在实践中,许多图像可以重叠):

  • opacity 属性的值:这定义了可以通过图像看到多少; opacity 值 0.0 表示图像完全透明,而 opacity 值 1.0 表示图像后面什么都看不到。
  • 每种颜色的 alpha 值和强度:这将颜色的透明度定义为 0.0-1.0 或 0-255 范围内的双精度值。
  • 混合模式,由 BlendMode 枚举值定义:根据每种颜色的模式、不透明度和 alpha 值,结果可能还取决于图像的顺序添加到场景中;第一个添加的图像称为底部输入,而重叠图像中的第二个称为顶部输入。如果顶部输入完全不透明,则底部输入被顶部输入隐藏。

重叠区域的最终外观是根据不透明度、 颜色的 alpha 值、颜色的数值(强度)和混合模式计算得出的,可以是以下之一:

  • ADD:将顶部输入的颜色和 alpha 分量添加到底部输入的分量中。
  • BLUE:将底部输入的蓝色分量替换为顶部输入的蓝色分量;其他颜色分量不受影响。
  • COLOR_BURN:底部输入颜色分量的倒数除以顶部输入颜色分量,然后将所有这些分量反转以产生结果颜色。
  • COLOR_DODGE:底部输入颜色分量除以顶部输入颜色分量的倒数以产生结果颜色。
  • DARKEN:从两个输入中选择较暗的颜色分量来生成结果颜色。
  • DIFFERENCE:从两个输入中较暗的颜色分量从较亮的分量中减去,以产生结果颜色。
  • EXCLUSION:将两个输入的颜色分量相乘和加倍,然后从底部输入颜色分量的总和中减去,以产生结果颜色。
  • GREEN:将底部输入的绿色分量替换为顶部输入的绿色分量;其他颜色分量不受影响。
  • HARD_LIGHT:输入颜色分量是相乘还是筛选,取决于顶部输入颜色。
  • LIGHTEN:选择来自两个输入的颜色分量中较亮的一个来产生结果颜色。
  • MULTIPLY:第一个输入的 颜色分量乘以第二个输入的颜色分量。
  • OVERLAY:根据底部输入颜色,输入颜色分量是相乘还是加网。
  • RED:将底部输入的红色分量替换为顶部输入的红色分量;其他颜色分量不受影响。
  • SCREEN:来自两个输入的颜色分量被反转,相互相乘,并且该结果再次反转以产生结果颜色。
  • SOFT_LIGHT:输入颜色分量变暗或变亮,取决于顶部输入颜色。
  • SRC_ATOP:位于底部输入内部的顶部输入部分与底部输入混合。
  • SRC_OVER:顶部输入与底部输入混合。

为了演示 Blend 效果,让我们创建另一个应用程序,名为 BlendEffect。它不需要 com.sun.* 包,因此不需要 --add-export VM 选项。只有 --module-path--add-modules 选项,在 中描述JavaFX 基础 部分,必须设置为编译和执行。

本书的范围不允许我们展示所有可能的组合,因此我们将创建一个红色圆圈和一个蓝色方块(参见 BlendEffect 类):

Circle createCircle(){
    Circle c = new Circle();
    c.setFill(Color.rgb(255, 0, 0, 0.5));
    c.setRadius(25);
    return c;
}
Rectangle createSquare(){
    Rectangle r = new Rectangle();
    r.setFill(Color.rgb(0, 0, 255, 1.0));
    r.setWidth(50);
    r.setHeight(50);
    return r;
}

我们使用 Color.rgb(int red, int green, int blue, double alpha) 方法来定义每个颜色的数字,但还有更多的方法可以做到这一点。阅读 Color 类 API 文档了解更多详情(https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/paint/Color.html)。

为了重叠创建的圆形和正方形,我们将使用 Group 节点:

Node c = createCircle();
Node s = createSquare();
Node g = new Group(s, c);

在前面的代码中,正方形是底部输入。我们还将创建一个组,其中正方形是顶部输入:

Node c = createCircle();
Node s = createSquare();
Node g = new Group(c, s);

区别很重要,因为我们将圆形定义为半透明,而正方形则 完全不透明。我们将在所有示例中使用相同的设置。

让我们比较一下 MULTIPLYSRC_OVER 这两种模式。我们将使用 setEffect() 方法将它们设置在组上,如下所示:

Blend blnd = new Blend();
blnd.setMode(BlendMode.MULTIPLY);
Node c = createCircle();
Node s = createSquare();
Node g = new Group(s, c);
g.setEffect(blnd);

在被调用的 BlendEffect 类的 start() 方法中,对于每种模式,我们创建两个组,其中一个输入为圆形在正方形的顶部,另一个带有输入的正方形在圆形的顶部,我们将四个创建的组放在 GridPane 布局中(参见详细的源代码)。如果我们运行 BlendEffect 应用程序,结果如下:

JDK17 |java17学习 第 11 章 网络编程

正如预期的那样,当正方形在顶部时(右侧的两个图像),重叠区域 完全被不透明的正方形占据。但是,当圆形是顶部输入时(左侧的两个图像),重叠区域在某种程度上是可见的,并且是根据混合效果计算得出的。

但是,如果我们直接在组上设置相同的模式,结果会略有不同。让我们运行相同的代码,但在组上设置模式:

Node c = createCircle();
Node s = createSquare();
Node g = new Group(c, s);
g.setBlendMode(BlendMode.MULTIPLY);JDK17 |java17学习 第 11 章 网络编程

start() 方法中找到以下代码:

      Node[] node = setEffectOnGroup(bm1, bm2);
      //Node[] node = setModeOnGroup(bm1, bm2);

并将其更改为以下内容:

      //Node[] node = setEffectOnGroup(bm1, bm2);
      Node[] node = setModeOnGroup(bm1, bm2);

如果我们再次运行 BlendEffect 类,结果将如下所示:

JDK17 |java17学习 第 11 章 网络编程

可以看到,圆圈的红色略有变化,MULTIPLYSRC_OVER 模式没有区别。这就是我们在本节开头提到的将节点添加到场景的顺序的问题。

结果还 根据设置效果的节点而变化。例如,我们只在圆上设置效果,而不是在组上设置效果:

Blend blnd = new Blend();
blnd.setMode(BlendMode.MULTIPLY);
Node c = createCircle();
Node s = createSquare();
c.setEffect(blnd);
Node g = new Group(s, c);

start() 方法中找到以下代码:

      Node[] node = setModeOnGroup(bm1, bm2);
      //Node[] node = setEffectOnCircle(bm1, bm2);

并将其更改为以下内容:

      //Node[] node = setModeOnGroup(bm1, bm2);
      Node[] node = setEffectOnCircle(bm1, bm2);

我们运行应用程序并看到以下内容:

JDK17 |java17学习 第 11 章 网络编程

右边的两个图像与前面所有示例中的相同,但左边的两个图像 显示了重叠区域的新颜色。现在,让我们在正方形而不是圆形上设置相同的效果,如下所示:

Blend blnd = new Blend();
blnd.setMode(BlendMode.MULTIPLY);
Node c = createCircle();
Node s = createSquare();
s.setEffect(blnd);
Node g = new Group(s, c);

start() 方法中找到以下代码:

      Node[] node = setEffectOnCircle(bm1, bm2);
      //Node[] node = setEffectOnSquare(bm1, bm2);

并将其更改为以下内容:

      //Node[] node = setEffectOnCircle(bm1, bm2);
      Node[] node = setEffectOnSquare(bm1, bm2); 

结果将再次略有变化,如下图所示:

JDK17 |java17学习 第 11 章 网络编程

MULTIPLYSRC_OVER 模式之间没有 区别,但是红色颜色与我们在圆圈上设置效果时不同。

我们可以再次改变方法,只在圆上直接设置混合模式,使用以下代码:

Node c = createCircle();
Node s = createSquare();
c.setBlendMode(BlendMode.MULTIPLY);

start() 方法中找到以下代码:

      Node[] node = setEffectOnSquare(bm1, bm2);
      //Node[] node = setModeOnCircle(bm1, bm2);

并将其更改为:

      //Node[] node = setEffectOnSquare(bm1, bm2);
      Node[] node = setModeOnCircle(bm1, bm2);

结果又变了:

JDK17 |java17学习 第 11 章 网络编程

在方块上设置混合模式只会再次消除 MULTIPLYSRC_OVER 模式之间的差异。

start() 方法中找到以下 代码:

      Node[] node = setModeOnCircle(bm1, bm2);
      //Node[] node = setModeOnSquare(bm1, bm2);

并将其更改为以下内容:

      //Node[] node = setModeOnCircle(bm1, bm2);
      Node[] node = setModeOnSquare(bm1, bm2);

结果如下:

JDK17 |java17学习 第 11 章 网络编程

为避免混淆并使混合结果更可预测,您必须注意将节点添加到场景中的顺序以及应用混合效果的方式的一致性。

在本书提供的源代码中,您将找到javafx.scene.effects包中包含的所有效果的示例。它们都通过并排比较得到证明。这是一个例子:

JDK17 |java17学习 第 11 章 网络编程

为方便起见,提供了 Pause 和 Continue 按钮,可让您暂停演示并查看不同值的结果在混合效果上设置不透明度。

为了演示所有其他效果,我们创建了另一个应用程序,称为 OtherEffects,它也不需要 com.sun.* 包,因此不需要 --add-export VM 选项。演示的效果包括 BloomBoxBlurColorAdjustDisplacementMap, DropShadow, 发光, InnerShadow, Lighting, MotionBlur, PerspectiveTransform, 反射ShadowToneSepiaTone。我们使用了两张图片来展示应用 每种效果的结果(Packt 徽标和山湖景观):

ClassLoader classLoader = 
              Thread.currentThread().getContextClassLoader(); 
String file = classLoader.getResource("packt.png").getFile(); FileInputStream inputP = new FileInputStream(file);
Image imageP = new Image(inputP);
ImageView ivP = new ImageView(imageP);
String file2 = classLoader.getResource("mount.jpeg").getFile(); FileInputStream inputM = new FileInputStream(file2);
Image imageM = new Image(inputM);
ImageView ivM = new ImageView(imageM);
ivM.setPreserveRatio(true);
ivM.setFitWidth(300);

我们还添加了两个按钮,允许您暂停和继续演示(它会迭代效果及其参数的值):

Button btnP = new Button("Pause");
btnP.setOnAction(e1 -> et.pause());
btnP.setStyle("-fx-background-color: lightpink;");
Button btnC = new Button("Continue");
btnC.setOnAction(e2 -> et.cont());
btnC.setStyle("-fx-background-color: lightgreen;");

et 对象是 EffectsThread 线程的对象:

EffectsThread et = new EffectsThread(txt, ivM, ivP);

线程遍历效果列表,创建对应的效果 10 次(具有 10 个不同的效果参数值),并且每次都在每个效果上设置创建的 Effect 对象图像,然后休眠 1 秒,让您有机会 查看结果:

public void run(){
    try {
        for(String effect: effects){
            for(int i = 0; i < 11; i++){
                double d = Math.round(i * 0.1 * 10.0) / 10.0;
                Effect e = createEffect(effect, d, txt);
                ivM.setEffect(e);
                ivP.setEffect(e);
                TimeUnit.SECONDS.sleep(1);
                if(pause){
                    while(true){
                        TimeUnit.SECONDS.sleep(1);
                        if(!pause){
                            break;
                        }
                    }
                }
            }
        }
        Platform.exit();
    } catch (Exception ex){
        ex.printStackTrace();
    }
}

我们将在带有效果结果的屏幕截图下展示接下来如何创建每个效果。为了呈现 结果,我们使用了 GridPane 布局:

GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setVgap(25);
grid.setPadding(new Insets(10, 10, 10, 10));
int i = 0;
grid.add(txt,    0, i++, 2, 1);
GridPane.setHalignment(txt, HPos.CENTER);
grid.add(ivP,    0, i++, 2, 1);
GridPane.setHalignment(ivP, HPos.CENTER);
grid.add(ivM,    0, i++, 2, 1);
GridPane.setHalignment(ivM, HPos.CENTER);
grid.addRow(i++, new Text());
HBox hb = new HBox(btnP, btnC);
hb.setAlignment(Pos.CENTER);
hb.setSpacing(25);
grid.add(hb,    0, i++, 2, 1);
GridPane.setHalignment(hb, HPos.CENTER);

最后,将创建的 GridPane 对象传递给场景,然后将其放置在我们之前示例中熟悉的舞台上:

Scene scene = new Scene(grid, 450, 500);
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX effect demo");
primaryStage.onCloseRequestProperty()
    .setValue(e3 -> System.out.println("Bye! See you later!"));
primaryStage.show();

以下 屏幕截图描述了 13 个参数值中每个参数值的效果示例。在每个屏幕截图下,我们展示了创建此效果的 createEffect(String effect, double d, Text txt) 方法的代码片段:

  • 参数值 1 的影响:
JDK17 |java17学习 第 11 章 网络编程
//double d = 0.9;
txt.setText(effect + ".threshold: " + d);
Bloom b = new Bloom();
b.setThreshold(d);
  • 参数值2的效果
JDK17 |java17学习 第 11 章 网络编程
// double d = 0.3;
int i = (int) d * 10;
int it = i / 3;
txt.setText(effect + ".iterations: " + it);
BoxBlur bb = new BoxBlur();
bb.setIterations(i);
  • 参数值3的效果
JDK17 |java17学习 第 11 章 网络编程
double c = Math.round((-1.0 + d * 2) * 10.0) / 10.0;     // 0.6
txt.setText(effect + ": " + c);
ColorAdjust ca = new ColorAdjust();
ca.setContrast(c);
  • 参数值4的效果:
JDK17 |java17学习 第 11 章 网络编程
double h = Math.round((-1.0 + d * 2) * 10.0) / 10.0;     // 0.6
txt.setText(effect + ": " + h);
ColorAdjust ca1 = new ColorAdjust();
ca1.setHue(h);
  • 参数值5的效果:
JDK17 |java17学习 第 11 章 网络编程
double st = Math.round((-1.0 + d * 2) * 10.0) / 10.0;    // 0.6
txt.setText(effect + ": " + st);
ColorAdjust ca3 = new ColorAdjust();
ca3.setSaturation(st);
  • 参数值6的效果:
JDK17 |java17学习 第 11 章 网络编程
int w = (int)Math.round(4096 * d);  //819
int h1 = (int)Math.round(4096 * d); //819
txt.setText(effect + ": " + ": width: " + w + ", height: " + 
                                                           h1);
DisplacementMap dm = new DisplacementMap();
FloatMap floatMap = new FloatMap();
floatMap.setWidth(w);
floatMap.setHeight(h1);
for (int k = 0; k < w; k++) {
    double v = (Math.sin(k / 20.0 * Math.PI) - 0.5) / 40.0;
    for (int j = 0; j < h1; j++) {
        floatMap.setSamples(k, j, 0.0f, (float) v);
    }
}
dm.setMapData(floatMap);
  • 参数值 7 的影响:
JDK17 |java17学习 第 11 章 网络编程
double rd = Math.round((127.0 * d) * 10.0) / 10.0; // 127.0
System.out.println(effect + ": " + rd);
txt.setText(effect + ": " + rd);
DropShadow sh = new DropShadow();
sh.setRadius(rd);
  • 参数值8的效果:
JDK17 |java17学习 第 11 章 网络编程
double rad = Math.round(12.1 * d *10.0)/10.0;      // 9.7
double off = Math.round(15.0 * d *10.0)/10.0;      // 12.0
txt.setText("InnerShadow: radius: " + rad + ", offset:" + off);
InnerShadow is = new InnerShadow();
is.setColor(Color.web("0x3b596d"));
is.setOffsetX(off);
is.setOffsetY(off);
is.setRadius(rad);
  • 参数值9的效果:
JDK17 |java17学习 第 11 章 网络编程
double sS = Math.round((d * 4)*10.0)/10.0;      // 0.4
txt.setText(effect + ": " + sS);
Light.Spot lightSs = new Light.Spot();
lightSs.setX(150);
lightSs.setY(100);
lightSs.setZ(80);
lightSs.setPointsAtX(0);
lightSs.setPointsAtY(0);
lightSs.setPointsAtZ(-50);
lightSs.setSpecularExponent(sS);
Lighting lSs = new Lighting();
lSs.setLight(lightSs);
lSs.setSurfaceScale(5.0);
  • 参数值10的效果:
JDK17 |java17学习 第 11 章 网络编程
double r = Math.round((63.0 * d)*10.0) / 10.0;      // 31.5
txt.setText(effect + ": " + r);
MotionBlur mb1 = new MotionBlur();
mb1.setRadius(r);
mb1.setAngle(-15);
  • 参数值11的效果
JDK17 |java17学习 第 11 章 网络编程
// double d = 0.9;
txt.setText(effect + ": " + d); 
PerspectiveTransform pt =
        new PerspectiveTransform(0., 1. + 50.*d, 310., 50. - 
       50.*d, 310., 50. + 50.*d + 1., 0., 100. - 50. * d + 2.);
  • 参数值12的效果:
JDK17 |java17学习 第 11 章 网络编程
// double d = 0.6;
txt.setText(effect + ": " + d);
Reflection ref = new Reflection();
ref.setFraction(d);
  • 参数值13的效果:
JDK17 |java17学习 第 11 章 网络编程
// double d = 1.0;
txt.setText(effect + ": " + d);
SepiaTone sep = new SepiaTone();
sep.setLevel(d);

本演示的完整源代码 随本书提供,可在 GitHub 上获取。

Summary

本章向您介绍了 JavaFX 工具包、它的主要特性以及如何使用它来创建 GUI 应用程序。涵盖的主题包括 Java GUI 技术概述、JavaFX 控件元素、图表、使用 CSS、FXML、嵌入 HTML、播放媒体和添加效果。

现在,您可以使用 Java GUI 技术创建用户界面,也可以创建用户界面项目并将其用作独立应用程序。

下一章专门介绍函数式编程。它概述了 JDK 附带的函数式接口,解释了 Lambda 表达式是什么,以及如何在 Lambda 表达式中使用函数式接口。它还解释和演示了如何使用方法引用。

Quiz

  1. JavaFX 中的顶级内容容器是什么?
  2. JavaFX 中所有场景参与者的基类是什么?
  3. 命名 JavaFX 应用程序的基类。
  4. JavaFX 应用程序必须实现的一种方法是什么?
  5. Application 方法必须由 main 方法调用才能执行 JavaFX 应用程序?
  6. 执行 JavaFX 应用程序需要哪两个 VM 选项?
  7. 使用右上角的 x 按钮关闭 JavaFX 应用程序窗口时会调用哪个 Application 方法?
  8. 必须使用哪个类来嵌入 HTML?
  9. 说出三个必须用来播放媒体的类。
  10. 播放媒体需要添加什么 VM 选项?
  11. 说出五种 JavaFX 效果。