JavaFX - Scene Builder 16 未正确加载自定义控件(无错误)

JavaFX - Scene Builder 16 not loading Custom Control properly (no errors)

this tutorial之后,我做了一个自定义控件

自定义控件:

package com.oof.bruh.controls;

import com.oof.bruh.controllers.CustomController;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.layout.*;

public class CustomControl extends VBox {

    CustomController controller;

    public CustomControl() {
        super();

        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/custom_control.fxml"));
            // Creating new controller
            controller = new CustomController();
            // Hooking up the controller to the 'custom_control.fxml'
            loader.setController(controller);
            // Create a redundant Node in which to load the fxml
            Node node = loader.load();
            this.getStylesheets().add(this.getClass().getResource("/css/style.css").toExternalForm());

            this.getChildren().add(node);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

自定义控制器

package com.oof.bruh.controllers;

import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.kordamp.ikonli.javafx.FontIcon;

import java.net.URL;
import java.util.ResourceBundle;

public class CustomController implements Initializable {

    @FXML
    VBox container;
    @FXML
    HBox titleContainer;
    @FXML
    Label title;
    @FXML
    FontIcon btnClock;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {

        btnClock.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
            
                System.out.println("Clock clicked");
            }
        });
    }

    public VBox getContainer() {
        return container;
    }

    public void setContainer(VBox container) {
        this.container = container;
    }

    public HBox getTitleContainer() {
        return titleContainer;
    }

    public void setTitleContainer(HBox titleContainer) {
        this.titleContainer = titleContainer;
    }

    public Label getTitle() {
        return title;
    }

    public void setTitle(Label title) {
        this.title = title;
    }

    public FontIcon getBtnClock() {
        return btnClock;
    }

    public void setBtnClock(FontIcon btnClock) {
        this.btnClock = btnClock;
    }
}

FXML 内容

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?import org.kordamp.ikonli.javafx.FontIcon?>

<VBox fx:id="container" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="218.0" spacing="2.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <HBox fx:id="titleContainer">
         <children>
            <Label fx:id="title" text="&lt;title&gt;" />
            <Pane HBox.hgrow="ALWAYS" />
            <FontIcon fx:id="btnClock" iconLiteral="mdi2c-clock-outline" />
         </children>
      </HBox>
   </children>
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
</VBox>

Gradle 依赖关系

dependencies {
    implementation "org.openjfx:javafx-base:11.0.2:${platform}"
    implementation "org.openjfx:javafx-controls:11.0.2:${platform}"
    implementation "org.openjfx:javafx-graphics:11.0.2:${platform}"
    implementation "org.openjfx:javafx-fxml:11.0.2:${platform}"
    implementation "org.kordamp.ikonli:ikonli-javafx:12.2.0"
    implementation "org.kordamp.ikonli:ikonli-materialdesign2-pack:12.2.0"
}

问题

当我导出 .jar 并将其从 JAR/FXML Manager 选项导入 Scene Builder 时,它会检测到它,但在预览,根据我的了解,这意味着某些内容未正确加载。

我检查了自定义库文件夹 -> 显示 JAR 分析报告。那里什么都没有。

当我添加它并将其拖到场景中时会发生什么?

  1. 如果添加到 Container(例如 Pane),则其 widthheight0(零)。即使手动变大,也找不到“子组件”。
  2. 如果直接添加,它具有 VBox 的默认布局设置,但仍然没有任何显示。

如果我run文件使用Gradle,一切正常

预期结果

能够添加多个自定义 FXML 制造组件耦合在 .jar

我试过的东西

  1. 如果我从 Gradle 生成的 build 文件夹中取出 custom_control.fxml 并以与 .jar 相同的方式将其添加到 Scene Builder 中,它会添加它没有问题。它不仅具有适当的布局尺寸,而且我什至可以 select 每个“子组件”单独。
  2. 以编程方式创建所有内容,生成 .jar 并将其导入到 Scene Builder 中,可以很好地加载控件,但我无法访问每个“子组件”。
  3. 我选择了 <fx:root> 路线,如 this answer here 和许多其他类似路线所述。当我通过 Gradle run 它时,它会工作,但它仍然不会出现在 Scene Builder 中。
  4. 添加 loader.setClassLoader(getClass().getClassLoader());,正如“Brad Turek”在 this question 中提到的那样,尽管我没有收到任何 ClassNotFoundException 错误。
  5. 不在CustomControl中加入CustomController,而是直接放在custom_control.fxml文件中。再一次,使用 Gradle 会 run 很好,但是当添加到 Scene Builder 时,预览部分没有任何内容。
  6. 多次重新启动 Scene Builder。
  7. 我关注了this tutorial, which seems to be outdated, thinking perhaps adding a Skin would help somehow. Unfortunately, trying to extend BehaviorBase or using setSkinClassName(CustomControlSkin.class.getName());, seems to lead to Cannot find symbol 'XXXXXXXX'. Eventually I stumbled upon this GitHub example of a DateTimePicker which implemented a Skin, after also reading UI Controls Architecture。即使我设法为它制作了 Skin,它仍然无法在 Scene Builder 中正确显示。
  8. 我按照 , making sure to cover the "Component Pre-requisites" section, and here 中的说明进行操作,但无济于事,即同样的问题仍然存在,run 使用 Gradle 没问题 -> Scene 中的组件预览部分没有任何内容生成器。
  9. CustomControl 中的每一行之后使用旧的 System.out.println("...") 方式,认为它可能会出现在 JAR 分析报告 中,但它没有。

可能的问题

我在想可能没有加载某些东西,但如果没有错误,我发现很难指出确切的位置。

根据 José Pereda 的建议,我通过终端 运行 Scene Builder,因此我遇到了以下错误:

> com.oof.bruh.controls.CustomControl - OK

javafx.fxml.LoadException: 
file:/path/to/my/project/files/bruh/build/libs/bruh-1.0.jar!/fxml/custom_control.fxml
at javafx.fxml/javafx.fxml.FXMLLoader.constructLoadException(Unknown Source)
    at javafx.fxml/javafx.fxml.FXMLLoader.importClass(Unknown Source)
    at javafx.fxml/javafx.fxml.FXMLLoader.processImport(Unknown Source)
    at javafx.fxml/javafx.fxml.FXMLLoader.processProcessingInstruction(Unknown Source)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source)
    at javafx.fxml/javafx.fxml.FXMLLoader.load(Unknown Source)
    at com.oof.bruh.controls.CustomControl.<init>(CustomControl.java:22)
    .
    .
    .
    .
    .
Caused by: java.lang.ClassNotFoundException: org.kordamp.ikonli.javafx.FontIcon
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
    at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadTypeForPackage(Unknown Source)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadType(Unknown Source)
    ... 127 more

这一行 at com.oof.bruh.controls.CustomControl.<init>(CustomControl.java:22)Node node = loader.load();.

看到这个错误后,我实施了我试过的方法部分提到的解决方案#4

package com.oof.bruh.controls;

import com.oof.bruh.controllers.CustomController;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.layout.*;

public class CustomControl extends VBox {

    CustomController controller;

    public CustomControl() {
        super();

        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/custom_control.fxml"));
            // Creating new controller
            controller = new CustomController();
            // Hooking up the controller to the 'custom_control.fxml'
            loader.setController(controller);
            loader.setClassLoader(getClass().getClassLoader());
            // Create a redundant Node in which to load the fxml
            Node node = loader.load();
            this.getStylesheets().add(this.getClass().getResource("/css/style.css").toExternalForm());

            this.getChildren().add(node);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我创建了 .jar,但随后出现了 java.util.zip.ZipException: invalid LOC header (bad signature) 错误。

感谢这里的,我决定做一个简单的Gradle clean -> build.

然后,当我将新的 .jar 添加到 Scene Builder 时,自定义控件终于出现了!不幸的是,我无权访问“子组件”。我不确定这是预期的行为还是另一个单独的问题。无论哪种方式,我都会先做一些研究。

为什么这个解决方案没有更早的工作?

我的想法是,很多时候,我并不是每次更改文件时都执行“clean -> build”,而是直接使用 jar 中的选项 Gradle.

.jar 文件可能已损坏,我可能在没有机会得到 ClassNotFoundException 错误之前不知不觉地得到了 ZipException

也许,在我实施 loader.setClassLoader(getClass().getClassLoader()); 的某个时候,我可能得到了 ZipException,所以即使我不会得到 ClassNotFoundException ,由于 .jar 状况不佳,Scene Builder 将无法使用其内容。

要记住的事情

  1. 在对这些文件进行任何更改后,如果需要任何生成的文件,请始终执行 Gradle clean -> build
  2. 如果使用了 Scene Builder 并且没有任何错误显示,请始终从命令行或终端启动它以查看可能的错误。