SceneBuilder 中 JavaFX 自定义组件的使用

gss*_*shj 4 java javafx scenebuilder openjfx

我正在使用 Java 20、JavaFX 20 和 Maven 创建一个小型个人项目。我在创建可重用组件并通过主场景的控制器操作它们时遇到问题。

首先,我按照官方文档中列出的步骤进行操作。之后,我转到 SceneBuilder 并在 SceneBuilder 中导入自定义组件的 FXML 文件(单击小引擎图标,其中显示“库”-> JAR/FXML Manager -> 从文件系统添加库/FXML)并将其添加到就像使用任何默认组件一样的场景。然后,我为自定义组件提供了一个 fx:id 并将其添加到场景的控制器类中,以便我可以使用它,但出现以下错误。

Exception in Application start method
java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:119)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
    at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1081)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:893)
    at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
    at java.base/java.lang.Thread.run(Thread.java:1623)
Caused by: javafx.fxml.LoadException: 
/C:/Users/user/Desktop/eclipse-workspace/Project 3/target/classes/app/views/fxml/Menu.fxml:43

    at javafx.fxml@20/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2722)
    at javafx.fxml@20/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2700)
    at javafx.fxml@20/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2563)
    at javafx.fxml@20/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2531)
    at app/app.Main.loadFXML(Main.java:29)
    at app/app.Main.start(Main.java:17)
    at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:839)
    at javafx.graphics@20/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:483)
    at javafx.graphics@20/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:456)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
    at javafx.graphics@20/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:455)
    at javafx.graphics@20/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at javafx.graphics@20/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics@20/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:185)
    ... 1 more
Caused by: java.lang.IllegalArgumentException: Can not set app.components.Custom field app.controllers.Menu.cc to javafx.scene.layout.VBox
    at java.base/jdk.internal.reflect.FieldAccessorImpl.throwSetIllegalArgumentException(FieldAccessorImpl.java:228)
    at java.base/jdk.internal.reflect.FieldAccessorImpl.throwSetIllegalArgumentException(FieldAccessorImpl.java:232)
    at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.set(MethodHandleObjectFieldAccessorImpl.java:115)
    at java.base/java.lang.reflect.Field.set(Field.java:834)
    at javafx.fxml@20/javafx.fxml.FXMLLoader.injectFields(FXMLLoader.java:1175)
    at javafx.fxml@20/javafx.fxml.FXMLLoader$ValueElement.processValue(FXMLLoader.java:870)
    at javafx.fxml@20/javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:764)
    at javafx.fxml@20/javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2853)
    at javafx.fxml@20/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2649)
    ... 13 more
Exception running application app.Main
Run Code Online (Sandbox Code Playgroud)

我注意到的一件奇怪的事情是,当我将组件添加到主场景时,它显示为 VBox 而不是 Custom,即使当我将其拖动到“层次结构”选项卡时,它显示组件的名称是 Custom,而不是 VBox。

这是Custom.java相关的文件

package app.components;

import java.io.IOException;

import app.Main;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;

public class Custom extends VBox {
    
    @FXML private Button plusBtn;
    @FXML private Button minusBtn;
    @FXML private Label label;
    
    public Custom() {
        FXMLLoader loader = new FXMLLoader(Main.class.getResource("components/Custom.fxml"));
        loader.setRoot(this);
        loader.setController(this);
        try {
            loader.load();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public void newText(String text) {
        label.setText(text);
    }
}

Run Code Online (Sandbox Code Playgroud)

自定义.fxml

<?xml version="1.0" encoding="UTF-8"?>

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

<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Button fx:id="plusBtn" mnemonicParsing="false" text="+" />
      <Label fx:id="label" text="Label" />
      <Button fx:id="minusBtn" mnemonicParsing="false" text="-" />
   </children>
</VBox>

Run Code Online (Sandbox Code Playgroud)

目前,我的主场景只是一个空的 StackPane,中间有我的自定义组件,我将“cc”指定为 fx:id。

菜单.java

package app.controllers;

import app.components.Custom;

public class Menu {

    @FXML
    private Custom cc;

        public void initialize() {
        cc.newText("Test");
    }
}
Run Code Online (Sandbox Code Playgroud)

模块信息.java

module app {
    requires javafx.controls;
    requires javafx.fxml;
    requires javafx.media;
    requires javafx.graphics;
    requires javafx.base;
    
    opens app to javafx.fxml;
    opens app.controllers to javafx.fxml;
    
    exports app;
}
Run Code Online (Sandbox Code Playgroud)

问题是,当我添加(从自定义部分拖放到 StackPane)我的组件时,它显示为 VBox 而不是自定义。这是一个屏幕截图,它可能会让我的意思更清楚:

图像

我希望该组件显示为自定义,而不是 VBox,因为 SceneBuilder 告诉我,即使我的控制器类中有该字段,它也找不到“cc”的可注入字段。

jew*_*sea 5

这个答案很长,但是发生了很多事情,所以就是这样。

这些步骤对我有用。如果严格遵循,它可能会对您有用。如果您以任何方式偏离这些步骤,则不能保证它会起作用,也不保证我会为您提供很大的支持。

我只是要注意要做什么,而不做太多解释。

基本方法是:

  1. 使用所需的自定义控件创建一个模块。
  2. 将带有自定义控件的模块导入到 SceneBuilder 中。
  3. 使用 SceneBuilder 使用自定义控件来设计您的应用程序。
  4. 为使用 SceneBuilder 生成的 FXML 的应用程序创建一个新项目。新项目的功能取决于自定义控制模块。
  5. 构建并运行应用程序项目。

Idea可以创建并使用多模块maven项目,因此多个模块可以在一个项目中(这可能是一个合理的方法),但是该设置更复杂并且不是解决问题的核心,所以我这样做了不在这里记录它。

步骤 1:为您的自定义组件创建 JavaFX 项目

在 Idea -> 新项目 -> JavaFX 项目 -> (使用 Maven) -> 组名称“com.example”工件名称“custom-component”中。

将生成的 java 源文件 fxml 和 pom.xml 替换为以下文件。

src/main/java/com/example/customcomponent/CustomComponent.java

package com.example.customcomponent;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;

import java.io.IOException;

public class CustomComponent extends VBox {
    
    @FXML private Button plusBtn;
    @FXML private Button minusBtn;
    @FXML private Label label;
    
    public CustomComponent() {
        FXMLLoader loader = new FXMLLoader(
                CustomComponent.class.getResource(
                        "custom-component.fxml"
                )
        );
        loader.setRoot(this);
        loader.setController(this);
        try {
            loader.load();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public void newText(String text) {
        label.setText(text);
    }
}
Run Code Online (Sandbox Code Playgroud)

src/main/java/module-info.java

module com.example.customcomponent {
    requires javafx.controls;
    requires javafx.fxml;

    opens com.example.customcomponent to javafx.fxml;
    exports com.example.customcomponent;
}
Run Code Online (Sandbox Code Playgroud)

src/main/resources/com/example/customcomponent/custom-component.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<fx:root type="javafx.scene.layout.VBox" alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <Button fx:id="plusBtn" mnemonicParsing="false" text="+" />
        <Label fx:id="label" text="Label" />
        <Button fx:id="minusBtn" mnemonicParsing="false" text="-" />
    </children>
</fx:root>
Run Code Online (Sandbox Code Playgroud)

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>custom-component</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>custom-component</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <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.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
Run Code Online (Sandbox Code Playgroud)

更改后重新导入maven项目。

转到 maven 窗口并执行 maven -> install。

第 2 步:将自定义组件导入 SceneBuilder

下载并安装 SceneBuilder 19。

在 SceneBuilder 中创建一个新的(空)项目。

单击库搜索字段旁边的齿轮图标。

JAR/FXML 管理器

选择“JAR/FXML 管理器”。

选择“从存储库手动添加库”。

输入组 ID:“com.example”、工件 ID:“自定义组件”,然后按 TAB 键。

从下拉列表中选择版本:“1.0-SNAPSHOT(本地)”。

从存储库手动添加库

选择“添加 JAR”。

“导入”对话框将显示“CustomComponent”,旁边有一个勾号以及该组件的预览图像。

导入对话框

保留新组件的默认大小设置,然后选择“导入组件”。

新安装的库将列在“库管理器”对话框中。

已安装的库

关闭“库管理器”对话框。

第 3 步:使用自定义组件设计应用程序 UI

转到库搜索字段,输入“StackPane”。

将 StackPane 拖到您正在创建的场景中。

转到库搜索字段,输入“CustomComponent”。

将 CustomComponent 拖到 StackPane 的中心。

单击 CustomComponent,然后单击“Code”面板并输入“fx:id”作为:“customComponent”。

单击控制器窗格并输入控制器类名称“com.example.customcomponentdemo.HelloController”。

将 FXML 文件保存为“hello-view.xml”,它将如下所示:

SceneBuilder 中的 hello-view

<?xml version="1.0" encoding="UTF-8"?>

<?import com.example.customcomponent.CustomComponent?>
<?import javafx.scene.layout.StackPane?>


<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.customcomponentdemo.HelloController">
   <children>
      <CustomComponent fx:id="customComponent" />
   </children>
</StackPane>
Run Code Online (Sandbox Code Playgroud)

步骤 4:使用自定义组件为您的应用程序创建 JavaFX 项目

在 Idea -> 新 JavaFX 项目 (maven) -> 组 id: "com.example", 工件 id: "custom-component-demo" -> 替换生成的文件,如下所示:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>custom-component-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>custom-component-demo</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>20</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>20</version>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>custom-component</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>20</source>
                    <target>20</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
Run Code Online (Sandbox Code Playgroud)

(编辑pom.xml后重新导入maven项目)

src/main/java/module-info.java

module com.example.customcomponentdemo {
    requires javafx.controls;
    requires javafx.fxml;
    requires com.example.customcomponent;

    opens com.example.customcomponentdemo to javafx.fxml;
    exports com.example.customcomponentdemo;
}
Run Code Online (Sandbox Code Playgroud)

src/main/resources/com/example/customcomponentdemo/hello-view.fxml

使用您从 SceneBuilder 保存的文件。

src/main/java/com/example/customcomponentdemo/HelloApplication.java

与生成的代码相同:

package com.example.customcomponentdemo;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 320, 240);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}
Run Code Online (Sandbox Code Playgroud)

src/main/java/com/example/customcomponentdemo/HelloController.java

package com.example.customcomponentdemo;

import com.example.customcomponent.CustomComponent;
import javafx.fxml.FXML;

public class HelloController {
    @FXML
    private CustomComponent customComponent;

    @FXML
    private void initialize() {
        customComponent.newText("xyzzy");
    }
}
Run Code Online (Sandbox Code Playgroud)

第 5 步:运行您的应用程序

双击 HelloApplication.java 文件,然后右键单击以构建应用程序并运行它。

该应用程序将显示您的自定义组件以及由应用程序控制器初始化的组件文本。

具有自定义组件的应用程序

关于版本的重要说明

仔细查看自定义组件项目文件中的 Java 源版本和目标版本,它列出了 17 个,而不是 20 个。此外,JavaFX 版本是 19。这些版本设置为与所使用的 SceneBuilder 版本相匹配。SceneBuilder 19 只能理解 JavaFX 19 组件(及更低版本)并在 JDK 17 上运行,因此只能理解编译为 Java 17(或更低版本)的 JAR 文件。如果您尝试使用比 SceneBuilder 理解的更高的目标 JDK 来构建自定义组件,那么它将无法在 JAR 文件中找到您的组件来导入它。

正如您所看到的,执行项目没有相同的限制,因此它可以在 JavaFX 20 和 JDK 20 上自由运行,没有任何问题。

有关的