JavaRush /Java 博客 /Random-ZH /Java FX 简介

Java FX 简介

已在 Random-ZH 群组中发布
有一天,我想到编写一个小型桌面应用程序来满足我的需求 - 就像一本用于学习外语单词的小词典 - 我开始绞尽脑汁,我该怎么做呢?自然,我第一个想到的就是Swing。想必大家都听说过Swing。这是一个用于创建用户图形界面的库。由于我们心爱的 Oracle 还没有完全放弃 Swing,所以它并没有被认为是过时的,应用程序仍然在其上运行。然而,Swing 不再对其进行现代化改造,Oracle 的人员已经让我们领略了 JavaFX 的未来。而事实上,JavaFX 使用 Swing 组件作为服务提供者)

什么是 JavaFX?

JavaFX 本质上是一个 Java 的 GUI 工具包。这里有一个小题外话,我们会记住GUI是什么: 图形用户界面- 图形用户界面是一种用户界面,其中所有元素(按钮、菜单、图标、列表)在界面上呈现给用户展示以图片、图形的形式进行。与命令行界面不同,在 GUI 中,用户可以使用输入设备随机访问可见对象。通常,界面元素以隐喻的形式实现,并显示其属性和目的以方便用户理解。 JavaFX旨在用 Java 创建游戏和桌面应用程序。事实上,由于针对 Java 提出了新的 GUI 工具,它将取代 Swing。此外,它还允许我们使用 CSS 设置 GUI 布局文件 (XML) 的样式并使其更加优雅,类似于我们在 Web 应用程序中所习惯的。JavaFX 还可以在单​​个 GUI 工具包中处理集成的 3D 图形以及音频、视频和嵌入式网络应用程序...它易于学习且经过良好优化。它支持许多操作系统,以及Windows、UNIX 系统和Mac OS。 Java FX 简介 - 3

JavaFX 特点:

  • JavaFX最初附带了大量的图形界面部分,例如各种按钮、文本字段、表格、树、菜单、图表等,这反过来又会节省我们大量的时间。
  • JavaFX 经常使用 CSS 样式,我们将能够使用特殊的 FXML 格式来创建 GUI,而不是使用 Java 代码。这使得快速布局 GUI 或更改外观或组成变得容易,而无需长时间使用 Java 代码。
  • JavaFX 具有现成的图表部分,因此当您需要基本图表时,我们不必从头开始编写它们。
  • JavaFX 另外还提供 3D 图形支持,如果我们正在开发某种游戏或类似应用程序,这通常很有用。
让我们看一下窗口的主要组件:
  • 舞台本质上是一个周围的窗口,充当起始画布并包含其余组件。一个应用程序可以有多个阶段,但无论如何都必须有一个这样的组件。本质上,Stage 是主要容器和入口点。
  • 场景- 显示舞台的内容(如嵌套娃娃)。每个阶段可以包含多个组件 - 场景,它们可以在它们之间切换。在内部,这是通过称为场景图的对象图实现的(其中每个元素都是一个节点,也称为Node)。
  • 节点是控件,例如标签按钮,甚至布局,它们内部可以有多个嵌套组件。每个场景可以有一个嵌套节点,但它可以是具有多个组件的布局。嵌套可以是多层的,布局包含其他布局和常规组件。每个这样的节点都有自己的标识符、样式、效果、状态和事件处理程序。
Java FX 简介 - 4 那么让我们稍微转向一下代码。由于我使用 Java 8,所以不需要提取任何依赖项,因为 JavaFx 默认位于 JDK 中(如 Java 9.10 中),但如果我们有 Java 11+,那么我们需要转到 Maven 存储库并从依赖项中提取它。

JavaFX:使用示例

我们创建一个带有方法main(入口点)的常规类:
public class AppFX extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
pimarySatge.show();
    }
}
这里我们的类继承自javafx.application.Application(我们从 Bugaga 盒子中继承)。在 main 中,我们调用静态 Application 方法launch()来启动我们的窗口。另外,我们的想法会抱怨我们没有实现 Application 方法,start而这正是我们最终要做的。它是做什么用的?并且为了能够管理属性(我们窗口的功能)。为此,我们使用传入参数primaryStage来调用方法show,以便我们可以看到正在启动的窗口main。让我们稍微填写一下我们的方法start
public void start(Stage primaryStage) throws Exception {
    primaryStage.setTitle("Dogs application");
    primaryStage.setWidth(500);
    primaryStage.setHeight(400);

    InputStream iconStream =
    getClass().getResourceAsStream("/images/someImage.png");
    Image image = new Image(iconStream);
    primaryStage.getIcons().add(image);

    Button button = new Button("WOF WOF ???'");

    button.setOnAction(e -< {
      Alert alert = new Alert(Alert.AlertType.INFORMATION, "WOF WOF WOF!!!");
        alert.showAndWait();
    });
    Scene primaryScene = new Scene(button);
    primaryStage.setScene(primaryScene);

    primaryStage.show();
}
那么我们在这里看到了什么?让我们逐行浏览一下: 2 - 设置窗口本身的名称(阶段) 3.4 - 设置其尺寸 6.7​​ - 设置读取流到文件的路径(图标) Java FX 简介 - 58 - 将文件创建为 Image 对象,它通过构造函数中传递的流连接到真实文件 9 - 在窗口的顶部面板中设置一个图标 11 - 创建一个按钮对象 13-16 - 设置按下按钮时的反应 17 - 创建一个场景,其中我们放置按钮 18 - 将场景放置在公共窗口上 20 - 设置窗口的可见性标志 结果我们得到一个小窗口来欢迎我们最喜欢的 pesels: Java FX 简介 - 6一切看起来都比 Swing 简单得多,不是吗?但事情还没有结束。编写所有代码来显示应用程序并不好;您需要以某种方式对其进行划分,以便使其更容易理解(图形组件放在一个篮子中,逻辑放在另一个篮子中)。这里 xml 出现了...... 天啊,xml?确切地。具体来说,我们使用 JavaFX - FXML 的特定实现,在其中定义应用程序的图形组件及其属性(各种大小等),然后将其连接到控制器,这有助于管理逻辑。让我们看一下这个 xml 的示例:
<?xml version="1.0" encoding="UTF-8"?>
<?language javascript?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox xmlns="http://javafx.com/javafx"  xmlns:fx="http://javafx.com/fxml"
   id="Dogs application" prefHeight="200" prefWidth="300" alignment="center">

  <Label text="Wow wow?"/>

  <Button fx:id="mainButton" text="Greeting" onAction="buttonClicked()"/>

    <fx:script>
       function buttonClicked() {
            mainButton.setText("Wow wow wow!!!")
       }
    </fx:script>
</VBox>
2 - 我们使用的脚本语言 4-6 - 导入的数据 8-9 Vbox - 将子组件放置在一行上的容器。11 - 显示一些文本 13 - 一个按钮,点击时,我们使用脚本中第15-18行描述的方法,方法中应该有调用这个xml文件的代码start,但现在这不是那么重要了,我们将省略它(下面将有一个拉取此文件的示例)。所以,xml当然好(但不是很好),手动编写它们很混乱,这不是上个世纪了吗? Java FX 简介 - 7

JavaFX SceneBuilder 简介

正是在这一点上(鼓声)开始发挥作用 -SceneBuilder 在 JavaFX Scene Builder中,我们可以使用它以图形界面的形式设计窗口,然后保存它们,并且该程序将根据结果,将构建我们将在我们的应用程序中改进它的 xml 文件。这个 fmxl 构建器的界面如下所示: Java FX 简介 - 8

一个小题外话。JavaFX 课程

我将跳过安装细节,并详细研究这个工具。这些都是值得进一步探讨的话题。因此,我仍然会留下一些有趣的 JavaFX 课程链接:一个(JavaFX 在线教程)和两个(另一个很好的教程)。让我们看一下我草拟的一个小例子。最后我得到了类似的东西: Java FX 简介 - 9
(有点像登记狗的窗口)
当您选择一只狗并按删除按钮时,该狗就会从我们的列表中删除。当您选择一个四足朋友并更改其字段时,按下“编辑”按钮后,狗的信息就会更新。当我们按下“新建”按钮时,会弹出一个窗口,为新狗创建一条记录(从它的名字开始): Java FX 简介 - 10然后单击“保存”并在第一个窗口中填写其其余字段,然后单击“编辑”按钮节省。听起来很容易,对吧?让我们看看这在我们的 Java 应用程序中会是什么样子。首先,我将在以下位置保留生成的这两个窗口的 xml 布局SceneBuilder首先(基本):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<AnchorPane prefHeight="300.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.tutorial.controller.BaseController">
   <children>
      <SplitPane dividerPositions="0.29797979797979796" prefHeight="300.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <items>
          <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
               <children>
                  <TableView fx:id="dogs" layoutX="-2.0" layoutY="-4.0" prefHeight="307.0" prefWidth="190.0" AnchorPane.bottomAnchor="-5.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-13.0" AnchorPane.topAnchor="-4.0">
                    <columns>
                      <TableColumn fx:id="nameList" prefWidth="100.33334350585938" text="Nickname" />
                    </columns>
                     <columnResizePolicy>
                        <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
                     </columnResizePolicy>
                  </TableView>
               </children>
            </AnchorPane>
          <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
               <children>
                  <Label layoutX="49.0" layoutY="25.0" text="Person Details" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0" />
                  <GridPane accessibleText="erreererer" gridLinesVisible="true" layoutX="5.0" layoutY="31.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="31.0">
                    <columnConstraints>
                      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                      <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                    </columnConstraints>
                    <rowConstraints>
                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                    </rowConstraints>
                     <children>
                        <Label prefHeight="17.0" prefWidth="70.0" text="Nickname" />
                        <Label text="Breed" GridPane.rowIndex="1" />
                        <Label text="Age" GridPane.rowIndex="2" />
                        <Label text="City" GridPane.rowIndex="3" />
                        <Label text="Level of training" GridPane.rowIndex="4" />
                        <TextField fx:id="breed" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                        <TextField fx:id="age" GridPane.columnIndex="1" GridPane.rowIndex="2" />
                        <TextField fx:id="city" GridPane.columnIndex="1" GridPane.rowIndex="3" />
                        <TextField fx:id="levelOfTraining" GridPane.columnIndex="1" GridPane.rowIndex="4" />
                        <TextField fx:id="name" GridPane.columnIndex="1" />
                     </children>
                  </GridPane>
                  <Button layoutX="251.0" layoutY="259.0" mnemonicParsing="false" onAction="#create" text="New" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="230.0" AnchorPane.rightAnchor="130.0" AnchorPane.topAnchor="260.0" />
                  <Button layoutX="316.0" layoutY="262.0" mnemonicParsing="false" onAction="#edit" text="Edit" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="290.0" AnchorPane.rightAnchor="70.0" AnchorPane.topAnchor="260.0" />
                  <Button layoutX="360.0" layoutY="262.0" mnemonicParsing="false" onAction="#delete" text="Delete" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="350.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="260.0" />
               </children>
            </AnchorPane>
        </items>
      </SplitPane>
   </children>
</AnchorPane>
第二(创造新的小狗):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="200.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.tutorial.controller.NewDogController">
   <children>
      <GridPane layoutX="31.0" layoutY="25.0" prefHeight="122.0" prefWidth="412.0">
        <columnConstraints>
          <ColumnConstraints hgrow="SOMETIMES" maxWidth="185.0" minWidth="10.0" prefWidth="149.0" />
          <ColumnConstraints hgrow="SOMETIMES" maxWidth="173.0" minWidth="10.0" prefWidth="146.0" />
        </columnConstraints>
        <rowConstraints>
          <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        </rowConstraints>
         <children>
            <Label prefHeight="48.0" prefWidth="178.0" text="Please, write name:">
               <font>
                  <Font size="20.0" />
               </font>
            </Label>
            <TextField fx:id="nickName" prefHeight="36.0" prefWidth="173.0" GridPane.columnIndex="1" />
         </children>
      </GridPane>
      <Button layoutX="222.0" layoutY="149.0" mnemonicParsing="false" onAction="#ok" prefHeight="37.0" prefWidth="95.0" text="Save" />
      <Button layoutX="325.0" layoutY="149.0" mnemonicParsing="false" onAction="#cansel" prefHeight="37.0" prefWidth="95.0" text="Cansel" />
   </children>
</AnchorPane>
文件夹结构是什么样的: Java FX 简介 - 11如您所见,没有什么特别的,有代表某些窗口的控制器,有代表我们数据的模型。让我们看一下启动应用程序的类(应用程序实现): @Data
public class AppFX extends Application {

    private Stage primaryStage;
    private AnchorPane rootLayout;
    private ObservableList listDog = FXCollections.observableArrayList();

    public AppFX() {
        listDog.add(new Dog("Fluffy", "Pug", 8, "Odessa", 2));
        listDog.add(new Dog("Archie", "Poodle", 3, "Lviv", 6));
        listDog.add(new Dog("Willie", "Bulldog", 5, "Kiev", 4));
        listDog.add(new Dog("Hector", "Shepherd", 9, "Minsk", 6));
        listDog.add(new Dog("Duncan", "Dachshund", 1, "Hogwarts", 9));
    }
在这里,我们看到一个构造函数,它将填充我们的初始数据(我们将其存储在一个特殊的表中 - ObservableList)。
public static void main(String[] args) {
        Application.launch();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        this.primaryStage = primaryStage;
        this.primaryStage.setTitle("Dogs application");
        showBaseWindow();
}
没什么特别的 -以及启动应用程序的 main实现:start()
public void showBaseWindow() {
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(AppFX.class.getResource("/maket/rootWindow.fxml"));
            rootLayout = loader.load();
            Scene scene = new Scene(rootLayout);
            primaryStage.setScene(scene);
            InputStream iconStream = getClass().getResourceAsStream("/icons/someImage.png");
            Image image = new Image(iconStream);
            primaryStage.getIcons().add(image);
            BaseController controller = loader.getController();
            controller.setAppFX(this);
            primaryStage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
因此,在这里我们看到了实际启动的方法start(),即设置基本窗口设置的方法。比如在资源中的xml布局中:给它一个图标,将它链接到特定的控制器,并给控制器一个到this类的链接)
public void showCreateWindow(Dog dog) {
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(AppFX.class.getResource("/maket/new.fxml"));
            AnchorPane page = loader.load();
            Stage dialogStage = new Stage();
            dialogStage.setTitle("Wow Wow Wow");
            dialogStage.initModality(Modality.WINDOW_MODAL);
            dialogStage.initOwner(primaryStage);
            dialogStage.setScene(new Scene(page));
            CreateController controller = loader.getController();
            controller.setDialogStage(dialogStage);
            controller.setDog(dog);
            dialogStage.showAndWait();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
这里我们看到负责第二个窗口出现的方法——用于创建新记录(新狗的名字)的窗口。我们还设置控制器、xml 布局、舞台等...我们将考虑的下一个类将是代表我们的狗的模型(有关它的信息): @Data
public class Dog {

    private StringProperty name;
    private StringProperty breed;
    private IntegerProperty age;
    private StringProperty city;
    private IntegerProperty levelOfTraining;

    public Dog(String name, String breed, int age, String city, int levelOfTraining) {
        this.name = new SimpleStringProperty(name);
        this.breed = new SimpleStringProperty(breed);
        this.age = new SimpleIntegerProperty(age);
        this.city = new SimpleStringProperty(city);
        this.levelOfTraining = new SimpleIntegerProperty(levelOfTraining);
    }

    public Dog() {
        name = new SimpleStringProperty();
        breed = null;
        age = null;
        city = null;
        levelOfTraining = null;
    }
}
这里我们看到两个构造函数。一个是几乎带有所有参数的常规构造函数(几乎是因为我们使用简单类型的特殊 FX 包装器),另一个是不带参数的构造函数:我们在创建一只新狗时使用它,它一开始只有一个姓名。基本窗口控制器: @Data
public class BaseController {

    @FXML
    private TableView dogs;
    @FXML
    private TableColumn nameList;
    @FXML
    private TextField name;
    @FXML
    private TextField breed;
    @FXML
    private TextField age;
    @FXML
    private TextField city;
    @FXML
    private TextField levelOfTraining;
    private AppFX appFX;
在这里我们看到我们的对象字段,但采用 TextField 格式。这是表示文本输入字段的格式。@FXML 是一个注释,旨在链接 Java 代码和布局的相应对象(按钮、字段或其他内容)。
@FXML
private void initialize() {
    nameList.setCellValueFactory(
            cellData -> cellData.getValue().getName());
    dogs.getSelectionModel().selectedItemProperty().addListener(
            (observable, oldValue, newValue) -> showDogsInformation(newValue));
}
在这里,我们在列表的右侧看到一个用于显示狗名称的方法(其 @FXML 注释绑定到 JavaFX TableView 布局组件)。
public void setAppFX(AppFX appFX) {
    this.appFX = appFX;
    dogs.setItems(appFX.getListDog());
}

private void showDogsInformation(Dog dog) {
    if (dog != null) {
        name.setText(dog.getName() != null ? dog.getName().getValue() : null);
        breed.setText(dog.getBreed() != null ? dog.getBreed().getValue() : null);
        age.setText(dog.getAge() != null ? String.valueOf(dog.getAge().get()) : null);
        city.setText(dog.getCity() != null ? dog.getCity().getValue() : null);
        levelOfTraining.setText(dog.getLevelOfTraining() != null ? String.valueOf(dog.getLevelOfTraining().get()) : null);
    } else {
        name.setText("");
        breed.setText("");
        age.setText("");
        city.setText("");
        levelOfTraining.setText("");
    }
}
在第一个方法中,我们看到设置对实现 Application 的类的内部引用(以便我们可以调用其方法来调用第二个窗口),并设置要显示的初始列表。第二个检查当前狗是否具有某些数据,并基于此设置文本字段:
@FXML
    private void delete() {
        int selectedIndex = dogs.getSelectionModel().getSelectedIndex();
        dogs.getItems().remove(selectedIndex);
    }

    @FXML
    private void edit() {
        int selectedIndex = dogs.getSelectionModel().getSelectedIndex();
        dogs.getItems().set(selectedIndex, new Dog(name.getText(), breed.getText(), Integer.valueOf(age.getText()), city.getText(), Integer.valueOf(levelOfTraining.getText())));
    }

    @FXML
    private void create() {
        Dog someDog = new Dog();
        appFX.showCreateWindow(someDog);
        if (someDog.getName() != null && !someDog.getName().getValue().isEmpty()) {
            appFX.getListDog().add(someDog);
        }
    }
}
这里我们看到与按钮关联的基本窗口的三个方法: Java FX 简介 - 12
  • 删除——按索引删除选中的(选定的)狗;
  • 编辑- 使用传输的数据创建一只新狗,并将其设置为代替之前的狗;
  • create - 我们创建一只新狗,并调用调用创建窗口的方法,传递一个新对象,关闭该对象后,如果名称不为空,则保存新宠物。
继续,用于创建狗的窗口控制器: @Data
public class CreateController {
    private Stage dialogStage;
    private Dog dog;

    @FXML
    private TextField nickName;

    @FXML
    private void ok() {
        if (nickName != null && !nickName.getText().isEmpty()) {
            dog.setName(new SimpleStringProperty(nickName.getText()));
            dialogStage.close();
        }
    }

    @FXML
    private void cansel() {
        dialogStage.close();
    }
}
在这里,我们看到与窗口中文本字段的连接,处理“保存”和“取消”按钮,从而以某种方式关闭窗口。正如您所看到的,为了在我的小型应用程序中提供更大的便利,我使用了 Lombok,否则代码会增长很多,而且我不会将其纳入我的审查中。 这可能就是我今天的全部内容了。今天我们简单地了解了基本概念和使用 JavaFX 的示例,并且我们可以构建小型桌面应用程序(使用附加信息,幸运的是,互联网上有丰富的信息)。而你,反过来,喜欢))
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION