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.