有一天,我想到编写一个小型桌面应用程序来满足我的需求 - 就像一本用于学习外语单词的小词典 - 我开始绞尽脑汁,我该怎么做呢?自然,我第一个想到的就是Swing。想必大家都听说过Swing。这是一个用于创建用户图形界面的库。由于我们心爱的 Oracle 还没有完全放弃 Swing,所以它并没有被认为是过时的,应用程序仍然在其上运行。然而,Swing 不再对其进行现代化改造,Oracle 的人员已经让我们领略了 JavaFX 的未来。而事实上,JavaFX 使用 Swing 组件作为服务提供者)
(有点像登记狗的窗口)
当您选择一只狗并按删除按钮时,该狗就会从我们的列表中删除。当您选择一个四足朋友并更改其字段时,按下“编辑”按钮后,狗的信息就会更新。当我们按下“新建”按钮时,会弹出一个窗口,为新狗创建一条记录(从它的名字开始): 然后单击“保存”并在第一个窗口中填写其其余字段,然后单击“编辑”按钮节省。听起来很容易,对吧?让我们看看这在我们的 Java 应用程序中会是什么样子。首先,我将在以下位置保留生成的这两个窗口的 xml 布局
这可能就是我今天的全部内容了。今天我们简单地了解了基本概念和使用 JavaFX 的示例,并且我们可以构建小型桌面应用程序(使用附加信息,幸运的是,互联网上有丰富的信息)。而你,反过来,喜欢))
什么是 JavaFX?
JavaFX 本质上是一个 Java 的 GUI 工具包。这里有一个小题外话,我们会记住GUI是什么: 图形用户界面- 图形用户界面是一种用户界面,其中所有元素(按钮、菜单、图标、列表)在界面上呈现给用户展示以图片、图形的形式进行。与命令行界面不同,在 GUI 中,用户可以使用输入设备随机访问可见对象。通常,界面元素以隐喻的形式实现,并显示其属性和目的以方便用户理解。 JavaFX旨在用 Java 创建游戏和桌面应用程序。事实上,由于针对 Java 提出了新的 GUI 工具,它将取代 Swing。此外,它还允许我们使用 CSS 设置 GUI 布局文件 (XML) 的样式并使其更加优雅,类似于我们在 Web 应用程序中所习惯的。JavaFX 还可以在单个 GUI 工具包中处理集成的 3D 图形以及音频、视频和嵌入式网络应用程序...它易于学习且经过良好优化。它支持许多操作系统,以及Windows、UNIX 系统和Mac OS。JavaFX 特点:
- JavaFX最初附带了大量的图形界面部分,例如各种按钮、文本字段、表格、树、菜单、图表等,这反过来又会节省我们大量的时间。
- JavaFX 经常使用 CSS 样式,我们将能够使用特殊的 FXML 格式来创建 GUI,而不是使用 Java 代码。这使得快速布局 GUI 或更改外观或组成变得容易,而无需长时间使用 Java 代码。
- JavaFX 具有现成的图表部分,因此当您需要基本图表时,我们不必从头开始编写它们。
- JavaFX 另外还提供 3D 图形支持,如果我们正在开发某种游戏或类似应用程序,这通常很有用。
- 舞台本质上是一个周围的窗口,充当起始画布并包含其余组件。一个应用程序可以有多个阶段,但无论如何都必须有一个这样的组件。本质上,Stage 是主要容器和入口点。
- 场景- 显示舞台的内容(如嵌套娃娃)。每个阶段可以包含多个组件 - 场景,它们可以在它们之间切换。在内部,这是通过称为场景图的对象图实现的(其中每个元素都是一个节点,也称为Node)。
- 节点是控件,例如标签按钮,甚至布局,它们内部可以有多个嵌套组件。每个场景可以有一个嵌套节点,但它可以是具有多个组件的布局。嵌套可以是多层的,布局包含其他布局和常规组件。每个这样的节点都有自己的标识符、样式、效果、状态和事件处理程序。
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 - 设置读取流到文件的路径(图标) 8 - 将文件创建为 Image 对象,它通过构造函数中传递的流连接到真实文件 9 - 在窗口的顶部面板中设置一个图标 11 - 创建一个按钮对象 13-16 - 设置按下按钮时的反应 17 - 创建一个场景,其中我们放置按钮 18 - 将场景放置在公共窗口上 20 - 设置窗口的可见性标志 结果我们得到一个小窗口来欢迎我们最喜欢的 pesels: 一切看起来都比 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当然好(但不是很好),手动编写它们很混乱,这不是上个世纪了吗?
JavaFX SceneBuilder 简介
正是在这一点上(鼓声)开始发挥作用 -SceneBuilder
在 JavaFX Scene Builder中,我们可以使用它以图形界面的形式设计窗口,然后保存它们,并且该程序将根据结果,将构建我们将在我们的应用程序中改进它的 xml 文件。这个 fmxl 构建器的界面如下所示:
一个小题外话。JavaFX 课程
我将跳过安装细节,并详细研究这个工具。这些都是值得进一步探讨的话题。因此,我仍然会留下一些有趣的 JavaFX 课程链接:一个(JavaFX 在线教程)和两个(另一个很好的教程)。让我们看一下我草拟的一个小例子。最后我得到了类似的东西: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>
文件夹结构是什么样的: 如您所见,没有什么特别的,有代表某些窗口的控制器,有代表我们数据的模型。让我们看一下启动应用程序的类(应用程序实现): @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);
}
}
}
这里我们看到与按钮关联的基本窗口的三个方法:
- 删除——按索引删除选中的(选定的)狗;
- 编辑- 使用传输的数据创建一只新狗,并将其设置为代替之前的狗;
- 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,否则代码会增长很多,而且我不会将其纳入我的审查中。
GO TO FULL VERSION