1. 简介
JavaFX是一个使用Java构建富客户端应用程序的库,它提供了一个API,用于设计可在几乎所有支持Java的设备上运行的GUI应用程序。
在本教程中,我们将重点介绍它的一些主要功能。
2. JavaFX API
在Java 8、9和10中,无需进行其他设置即可开始使用JavaFX库。从JDK 11开始,该项目将从JDK中移除,并且应将以下依赖和插件添加到pom.xml中:
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>19</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
2.1 架构
JavaFX使用硬件加速图形管道进行渲染,即Prism。此外,为了充分加速图形的使用,它利用软件或硬件渲染机制,内部使用DirectX和OpenGL。
JavaFX有一个依赖于平台的Glass窗口工具包层,用于连接到原生操作系统,它使用操作系统的事件队列来调度线程使用。此外,它还异步处理窗口、事件和计时器。
媒体和Web引擎支持媒体播放和HTML/CSS。
让我们看看JavaFX应用程序的主要结构是什么样的:
这里,我们注意到两个主要容器:
- Stage是应用程序的主要容器和入口点,它代表主窗口,并作为start()方法的参数传递。
- Scene是用于保存UI元素(例如图像视图、按钮、网格、文本框)的容器。
Scene可以被替换或切换到另一个Scene,这代表了一个由层级对象组成的图,称为“Scene图”。该层级中的每个元素称为一个节点,每个节点都有其ID、样式、效果、事件处理程序和状态。
此外,Scene还包含布局容器、图像、媒体。
2.2 线程
在系统级别,JVM创建单独的线程来运行和呈现应用程序:
- Prism渲染线程:负责单独渲染场景图。
- 应用程序线程:是任何JavaFX应用程序的主线程,所有活动节点和组件都连接到此线程。
2.3 生命周期
javafx.application.Application类具有以下生命周期方法:
- init():在Application实例创建后调用,此时,JavaFX API尚未准备就绪,因此我们无法在此处创建图形组件。
- start(Stage stage):所有图形组件都在这里创建,此外,图形活动的主线程也从这里启动。
- stop():在应用程序关闭之前调用;例如,当用户关闭主窗口时;重写此方法在应用程序终止之前进行一些清理工作很有用。
静态launch()方法启动JavaFX应用程序。
2.4 FXML
JavaFX使用特殊的FXML标记语言来创建视图界面。
这提供了一个基于XML的结构,用于将视图与业务逻辑分离。XML更适合于此,因为它能够非常自然地表示场景图的层次结构。
最后,为了加载.fxml文件,我们使用FXMLLoader类,它会生成场景层次结构的对象图。
3. 入门
为了实用,让我们构建一个允许搜索人员列表的小应用程序。
首先,让我们添加一个Person模型类来代表我们的域:
public class Person {
private SimpleIntegerProperty id;
private SimpleStringProperty name;
private SimpleBooleanProperty isEmployed;
// getters, setters
}
请注意,为了包装int、String和boolean值,我们使用javafx.beans.property包中的SimpleIntegerProperty、SimpleStringProperty、SimpleBooleanProperty类。
接下来,让我们创建扩展Application抽象类的Main类:
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("/SearchController.fxml"));
AnchorPane page = (AnchorPane) loader.load();
Scene scene = new Scene(page);
primaryStage.setTitle("Title goes here");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
我们的主类覆盖了start()方法,它是程序的入口点。
然后,FXMLLoader将对象图层次结构从SearchController.fxml加载到AnchorPane中。
启动新的Scene后,我们将其设置为主Stage。我们还设置了窗口的标题,并用show()方法显示它。
请注意,包含main()方法很有用,这样可以在没有JavaFX Launcher的情况下运行JAR文件。
3.1 FXML视图
现在让我们更深入地了解SearchController XML文件。
对于我们的搜索应用程序,我们将添加一个文本字段来输入关键字和搜索按钮:
<AnchorPane
xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="cn.tuyucheng.taketoday.view.SearchController">
<children>
<HBox id="HBox" alignment="CENTER" spacing="5.0">
<children>
<Label text="Search Text:"/>
<TextField fx:id="searchField"/>
<Button fx:id="searchButton"/>
</children>
</HBox>
<VBox fx:id="dataContainer"
AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0"
AnchorPane.topAnchor="50.0">
</VBox>
</children>
</AnchorPane>
AnchorPane是这里的根容器,也是图形层次结构的第一个节点。调整窗口大小时,它会将子项重新定位到其锚点。fx:controller属性将Java类与标签连接起来。
还有一些其他可用的内置布局:
- BorderPane:将布局分为五个部分:顶部、右侧、底部、左侧、中间
- HBox:在水平面板中排列子组件
- VBox:子节点排列在垂直列中
- GridPane:用于创建具有行和列的网格
在我们的示例中,在水平HBox面板内,我们使用Label放置文本,使用TextField放置输入框,并放置Button。我们使用fx:id标记这些元素,以便稍后在Java代码中使用它们。
VBox面板是我们显示搜索结果的地方。
然后,为了将它们映射到Java字段-我们使用@FXML注解:
public class SearchController {
@FXML
private TextField searchField;
@FXML
private Button searchButton;
@FXML
private VBox dataContainer;
@FXML
private TableView tableView;
@FXML
private void initialize() {
// search panel
searchButton.setText("Search");
searchButton.setOnAction(event -> loadData());
searchButton.setStyle("-fx-background-color: #457ecd; -fx-text-fill: #ffffff;");
initTable();
}
}
填充@FXML注解字段后,initialize()将自动调用。在这里,我们可以对UI组件执行进一步的操作,例如注册事件监听器、添加样式或更改文本属性。
在initTable()方法中,我们将创建包含结果的表格(有3列),并将其添加到dataContainer VBox:
private void initTable() {
tableView = new TableView<>();
TableColumn id = new TableColumn("ID");
TableColumn name = new TableColumn("NAME");
TableColumn employed = new TableColumn("EMPLOYED");
tableView.getColumns().addAll(id, name, employed);
dataContainer.getChildren().add(tableView);
}
最后,这里描述的所有逻辑将产生以下窗口:
4. Binding API
现在已经处理了视觉方面,让我们开始介绍绑定数据。
绑定API提供了一些接口,当另一个对象的值发生变化时通知该对象。
我们可以使用bind()方法或通过添加监听器来绑定一个值。
单向绑定仅提供单个方向的绑定:
searchLabel.textProperty().bind(searchField.textProperty());
在这里,搜索字段的任何变化都会更新标签的文本值。
相比之下,双向绑定会在两个方向上同步两个属性的值。
绑定字段的另一种方法是ChangeListeners:
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
searchLabel.setText(newValue);
});
Observable接口允许观察对象的值的变化。
为了举例说明这一点,最常用的实现是javafx.collections.ObservableList<T>接口:
ObservableList<Person> masterData = FXCollections.observableArrayList();
ObservableList<Person> results = FXCollections.observableList(masterData);
在这里,任何模型变化(如插入、更新或删除元素)都会立即通知UI控件。
masterData列表将包含Person对象的初始列表,而results列表将是我们在搜索时显示的列表。
我们还必须更新initTable()方法以将表中的数据绑定到初始列表,并将每一列连接到Person类字段:
private void initTable() {
tableView = new TableView<>(FXCollections.observableList(masterData));
TableColumn id = new TableColumn("ID");
id.setCellValueFactory(new PropertyValueFactory("id"));
TableColumn name = new TableColumn("NAME");
name.setCellValueFactory(new PropertyValueFactory("name"));
TableColumn employed = new TableColumn("EMPLOYED");
employed.setCellValueFactory(new PropertyValueFactory("isEmployed"));
tableView.getColumns().addAll(id, name, employed);
dataContainer.getChildren().add(tableView);
}
5. 并发
在场景图中操作UI组件并非线程安全,因为它只能从应用程序线程访问,javafx.concurrent包可以帮助实现多线程。
让我们看看如何在后台线程中执行数据搜索:
private void loadData() {
String searchText = searchField.getText();
Task<ObservableList<Person>> task = new Task<ObservableList<Person>>() {
@Override
protected ObservableList<Person> call() throws Exception {
updateMessage("Loading data");
return FXCollections.observableArrayList(masterData
.stream()
.filter(value -> value.getName().toLowerCase().contains(searchText))
.collect(Collectors.toList()));
}
};
}
在这里,我们创建一个一次性任务javafx.concurrent.Task对象并重写call()方法。
call()方法完全在后台线程运行,并将结果返回给应用程序线程,这意味着在此方法中对UI组件的任何操作都将引发运行时异常。
但是,可以调用updateProgress()和updateMessage()来更新Application线程中的元素,当任务状态转换为SUCCEEDED状态时,会从Application线程调用onSucceeded()事件处理程序:
task.setOnSucceeded(event -> {
results = task.getValue();
tableView.setItems(FXCollections.observableList(results));
});
在同一个回调中,我们将tableView数据更新到新的结果列表。
该Task是Runnable,因此要启动它,我们只需要使用task参数启动一个新线程:
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
setDaemon(true)标志表示线程完成工作后将终止。
6. 事件处理
我们可以将事件描述为应用程序可能感兴趣的动作。
例如,鼠标单击、键盘按下、窗口调整大小等用户操作由javafx.event.Event类或其任何子类处理或通知。
此外,我们区分了三种类型的事件:
- InputEvent:所有类型的按键和鼠标动作,如KEY_PRESSED、KEY_TYPED、KEY_RELEASED或MOUSE_PRESSES、MOUSE_RELEASED
- ActionEvent:代表各种动作,例如触发Button或完成KeyFrame
- WindowEvent:WINDOW_SHOWING、WINDOW_SHOWN
为了演示,下面的代码片段捕获了在searchField上按下Enter键的事件:
searchField.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
loadData();
}
});
7. 样式
我们可以通过应用自定义设计来更改JavaFX应用程序的UI。
默认情况下,JavaFX使用modena.css作为整个应用程序的CSS资源,它是jfxrt.jar的一部分。
要覆盖默认样式,我们可以向场景添加样式表:
scene.getStylesheets().add("/search.css");
我们还可以使用内联样式;例如,为特定节点设置样式属性:
searchButton.setStyle("-fx-background-color: slateblue; -fx-text-fill: white;");
8. 总结
这篇简短的文章涵盖了JavaFX API的基础知识,我们介绍了其内部结构,并介绍了其架构、生命周期和组件的关键功能。
Post Directory
