Issue
Following this tutorial, I made a custom control.
CustomControl:
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();
}
}
}
CustomController
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 Contents
<?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="<title>" />
<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
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"
}
Problem
When I export the .jar
and import it into the Scene Builder from the JAR/FXML Manager option, it detects it, but there's nothing in the preview, which from what I've come to learn, means something is not getting loaded properly.
I checked the Custom Library Folder -> Show JAR Analysis Report. There's nothing there.
What happens when I add it anyway and drag it into the scene?
- If added to a
Container
(e.g.Pane
), itswidth
andheight
are0
(zero). Even if made bigger manually, there are no "sub-components" to be found. - If added directly, it has the default layout settings for
VBox
, but still nothing showing.
If I run
the file using Gradle, everything works as expected.
Expected result
To be able to add multiple custom FXML
made components coupled in a .jar
.
Things I've tried
- If I take the
custom_control.fxml
from the Gradle generatedbuild
folder and add it in the Scene Builder the same way as the.jar
, it adds it with no issues. Not only does it have the proper layout dimensions, but I can even select each "sub-component" individually. - Creating everything programmatically, generating the
.jar
and importing it into the Scene Builder, loads the control fine, but I've no access to each "sub-component". - I took the
<fx:root>
route, as explained in this answer here and many other similar ones. When Irun
it via Gradle, it would work, but it still wouldn't show up in Scene Builder. - Adding
loader.setClassLoader(getClass().getClassLoader());
as mentioned by "Brad Turek" in this question, even though I didn't get anyClassNotFoundException
errors. - Not adding the
CustomController
inCustomControl
, but instead putting it in thecustom_control.fxml
file directly. Once again, it wouldrun
fine using Gradle, but when added to the Scene Builder, there's nothing in the preview section. - Restarting Scene Builder multiple times.
- I followed this tutorial, which seems to be outdated, thinking perhaps adding a
Skin
would help somehow. Unfortunately, trying to extendBehaviorBase
or usingsetSkinClassName(CustomControlSkin.class.getName());
, seems to lead toCannot find symbol 'XXXXXXXX'
. Eventually I stumbled upon this GitHub example of aDateTimePicker
which implemented aSkin
, after also reading UI Controls Architecture. Even though I managed to make aSkin
for it, it would STILL not show up properly in Scene Builder. - I followed the instructions in the extensive answer here, making sure to cover the "Component Pre-requisites" section, and here but to no avail, i.e. the same issue persists,
run
s fine using Gradle -> Nothing in the component preview section in Scene Builder. - Used the old
System.out.println("...")
way after each line in theCustomControl
thinking it might show up in JAR Analysis Report, but it didn't.
Possible issue
I'm thinking something might not be getting loaded along the way, but with no errors, I find it hard to pin point the exact place.
Solution
As suggested by José Pereda, I ran Scene Builder via terminal, thanks to which I was greeted by the following error:
> 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
This line at com.oof.bruh.controls.CustomControl.<init>(CustomControl.java:22)
being the Node node = loader.load();
.
After seeing this error, I implemented solution #4 mentioned in the Things I've tried section.
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();
}
}
}
I created the .jar
but subsequently, I got java.util.zip.ZipException: invalid LOC header (bad signature)
error.
Thanks to the answer here, I decided to do a simple Gradle clean
-> build
.
Then, when I added the new .jar
to Scene Builder, the custom control finally showed up! Unfortunately, I don't have access to the "sub-components". I'm not sure if it's an expected behavior or another separate issue. Either way, I'll do some research first.
Why didn't this solution work earlier?
My thinking is because, many of the times, I wasn't doing "clean
-> build
" every time I made a change to my files, but instead directly used the jar
option in Gradle.
There for the .jar
file might've been corrupted and I might've been getting the ZipException
without knowing it before even getting the chance to get the ClassNotFoundException
error.
Perhaps, at one of the times when I had the loader.setClassLoader(getClass().getClassLoader());
implemented, I might've been getting the ZipException
, so even if I wouldn't have gotten ClassNotFoundException
, since the .jar
was in a bad condition, the Scene Builder wouldn't have been able to use its contents.
Things to remember
- Always do Gradle
clean
->build
when any of the generated files are needed for something after any changes to those files. - If Scene Builder is used and no errors are shown anywhere, always start it from the command line or terminal to see for possible errors.
Answered By - DoombringerBG
Answer Checked By - Senaida (JavaFixing Volunteer)