I'm creating a small personal project using Java 20, JavaFX 20 and Maven. I'm having trouble creating reusable components and manipulating them through the main scene's controller.
First, I followed the steps listed in the official documentation. After that, I went to SceneBuilder and imported my custom component's FXML file in SceneBuilder (Click on the small engine icon where it says "Library" -> JAR/FXML Manager -> Add Library/FXML from file system) and added it to the scene like you would with any default component. I then gave my custom component a fx:id and added it to my scene's controller class so I can to stuff with it, but I get the following error.
Exception in Application start method
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:119)
at java.base/java.lang.reflect.Method.invoke(Method.java:578)
at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:578)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1081)
Caused by: java.lang.RuntimeException: Exception in Application start method
at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:893)
at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.base/java.lang.Thread.run(Thread.java:1623)
Caused by: javafx.fxml.LoadException:
/C:/Users/user/Desktop/eclipse-workspace/Project 3/target/classes/app/views/fxml/Menu.fxml:43
at javafx.fxml@20/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2722)
at javafx.fxml@20/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2700)
at javafx.fxml@20/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2563)
at javafx.fxml@20/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2531)
at app/app.Main.loadFXML(Main.java:29)
at app/app.Main.start(Main.java:17)
at javafx.graphics@20/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:839)
at javafx.graphics@20/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:483)
at javafx.graphics@20/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:456)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
at javafx.graphics@20/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:455)
at javafx.graphics@20/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at javafx.graphics@20/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics@20/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:185)
... 1 more
Caused by: java.lang.IllegalArgumentException: Can not set app.components.Custom field app.controllers.Menu.cc to javafx.scene.layout.VBox
at java.base/jdk.internal.reflect.FieldAccessorImpl.throwSetIllegalArgumentException(FieldAccessorImpl.java:228)
at java.base/jdk.internal.reflect.FieldAccessorImpl.throwSetIllegalArgumentException(FieldAccessorImpl.java:232)
at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.set(MethodHandleObjectFieldAccessorImpl.java:115)
at java.base/java.lang.reflect.Field.set(Field.java:834)
at javafx.fxml@20/javafx.fxml.FXMLLoader.injectFields(FXMLLoader.java:1175)
at javafx.fxml@20/javafx.fxml.FXMLLoader$ValueElement.processValue(FXMLLoader.java:870)
at javafx.fxml@20/javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:764)
at javafx.fxml@20/javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2853)
at javafx.fxml@20/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2649)
... 13 more
Exception running application app.Main
A weird thing I noticed is that when I add the component to the main scene, it shows up as a VBox and not a Custom even though when I drag it in the "Hierarchy" tab it says the component's name is Custom, not VBox.
Here are the files related Custom.java
package app.components;
import java.io.IOException;
import app.Main;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
public class Custom extends VBox {
@FXML private Button plusBtn;
@FXML private Button minusBtn;
@FXML private Label label;
public Custom() {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("components/Custom.fxml"));
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void newText(String text) {
label.setText(text);
}
}
Custom.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Button fx:id="plusBtn" mnemonicParsing="false" text="+" />
<Label fx:id="label" text="Label" />
<Button fx:id="minusBtn" mnemonicParsing="false" text="-" />
</children>
</VBox>
At the moment, my main scene is just an empty StackPane with my custom component in the center to which I gave "cc" as the fx:id.
Menu.java
package app.controllers;
import app.components.Custom;
public class Menu {
@FXML
private Custom cc;
public void initialize() {
cc.newText("Test");
}
}
module-info.java
module app {
requires javafx.controls;
requires javafx.fxml;
requires javafx.media;
requires javafx.graphics;
requires javafx.base;
opens app to javafx.fxml;
opens app.controllers to javafx.fxml;
exports app;
}
The problem is that when I add (drag and drop from Custom section to the StackPane) my component, it shows up as VBox and not Custom. Here's a screenshot, it might make what I mean clearer:
I want the component to show up just as Custom, not VBox, because SceneBuilder tells me that it doesn't find an injectable field for 'cc' even though I have the field in my controller class.