您可以在同一个 jar 中嵌套两个或多个自定义控件,这将在您的 IDE 或命令行中正常运行。
但是如果您从 Scene Builder 导入该 jar,如果某些自定义控件依赖于其他自定义控件,则它们可能无法导入。
这是有原因的,最好的部分是,还有一个简单的解决方案。
如何导入自定义控件?
如果您查看 Scene Builder 的 source code,要导入 jar 的可能自定义控件,则有一个 JarExplorer 类,它有一个 explore method。
这个方法基本上会遍历 jar 中的每个类,找出该类是否是应该添加到用户库中的可能的自定义组件。
最后,它的工作原理是尝试基于该类创建一个 FXML 对象:
entryClass = classLoader.loadClass(className);
instantiateWithFXMLLoader(entryClass, classLoader);
如果成功,则将该类添加到组件集合中。
为什么嵌套的自定义控件导入失败?
那么为什么一个嵌套的自定义控件,一个有另一个自定义控件作为依赖的控件,导入失败呢?可以通过调试 Scene Builder 并打印出您得到的异常来找到其原因:
try {
instantiateWithFXMLLoader(entryClass, classLoader);
} catch (RuntimeException | IOException x) {
status = JarReportEntry.Status.CANNOT_INSTANTIATE;
x.printStackTrace(); // <-- print exception
} catch (Error | ClassNotFoundException x) {
status = JarReportEntry.Status.CANNOT_LOAD;
x.printStackTrace(); // <-- print exception
}
当您创建任何类型的自定义控件时,记录这一点非常有用。
在嵌套自定义控件的情况下,如果依赖项已经在类路径中可用,并且类路径是指Scene Builder预先加载的所有依赖项,则不会有问题。但如果它不可用,FXMLLoader 将无法创建此控件的有效实例。
让我们试试你的SliderVariable 控件。如果您打印出异常,您将看到如下内容:
javafx.fxml.LoadException:
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2425)
at com.oracle.javafx.scenebuilder.kit.library.util.JarExplorer.instantiateWithFXMLLoader(JarExplorer.java:110)
... 9 more
Caused by: java.lang.RuntimeException: javafx.fxml.LoadException:
file:.../SliderVariable-1.0-SNAPSHOT-shaded!/com/coolcompany/slidervariable/SliderVariable.fxml
...
Caused by: java.lang.ClassNotFoundException: com.coolcompany.infoicon.InfoIcon
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at javafx.fxml.FXMLLoader.loadTypeForPackage(FXMLLoader.java:2916)
at javafx.fxml.FXMLLoader.loadType(FXMLLoader.java:2905)
at javafx.fxml.FXMLLoader.importClass(FXMLLoader.java:2846)
... 26 more
所以基本上这解释了为什么不导入嵌套控件:内部控件事先在类路径中不可用。
可能的解决方案
显然,您可以单独捆绑两个控件,首先导入InfoIcon 控件,然后只导入SliderVariable 控件。但是,如果您想在单个依赖项中分发这些控件,这可能会出现问题。
最佳解决方案
那么,如果两者都在同一个 jar 中,我们如何在运行时将内部控件提供给外部控件?
这是通过将此外部控件类的类加载器传递给FXMLLoader 来完成的。这可以通过调用外部控件中的FXMLLoader::setClassLoader 方法来完成。
在你的情况下:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("SliderVariable.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
// set FXMLLoader's classloader!
fxmlLoader.setClassLoader(getClass().getClassLoader());
try {
fxmlLoader.load();
} catch (IOException exception) { }
如果您再试一次,您将获得两个控件作为自定义组件。