JavaFX简介

2025/04/21

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的基础知识,我们介绍了其内部结构,并介绍了其架构、生命周期和组件的关键功能。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章