Reputation: 664
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?
Container
(e.g. Pane
), its width
and height
are 0
(zero). Even if made bigger manually, there are no "sub-components" to be found.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
custom_control.fxml
from the Gradle generated build
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..jar
and importing it into the Scene Builder, loads the control fine, but I've no access to each "sub-component".<fx:root>
route, as explained in this answer here and many other similar ones. When I run
it via Gradle, it would work, but it still wouldn't show up in Scene Builder.loader.setClassLoader(getClass().getClassLoader());
as mentioned by "Brad Turek" in this question, even though I didn't get any ClassNotFoundException
errors.CustomController
in CustomControl
, but instead putting it in the custom_control.fxml
file directly. Once again, it would run
fine using Gradle, but when added to the Scene Builder, there's nothing in the preview section.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. Even though I managed to make a Skin
for it, it would STILL not show up properly in Scene Builder.run
s fine using Gradle -> Nothing in the component preview section in Scene Builder.System.out.println("...")
way after each line in the CustomControl
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.
Upvotes: 1
Views: 872
Reputation: 664
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
clean
-> build
when any of the generated files are needed for something after any changes to those files.Upvotes: 3