I have implemented a custom control, using an fxml file and a Java class, similar to what is explained in this official tutorial (see the code bellow). Note that the fxml root element is defined with fx:root
and I call setRoot
programmatically.
I have tried including the control in the FXML layout of an application, and the application loads fine (and displays the control as expected).
However, if I try to import a jar file containing my control in Scene Builder 2.0, the control doesn't appear in the list of components to import (unlike some other controls in the same jar). If I select "Show JAR Analysis Report", it shows an error caused by javafx.fxml.LoadException: Root value already specified
.
Do you know why I get this error when loading in Scene Builder, even though it loads correctly in a real application?
Here is the FXML :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<fx:root type="javafx.scene.layout.GridPane" id="MediaMetadataDisplay" hgap="20.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0"
prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints fillWidth="false" hgrow="NEVER" maxWidth="-Infinity" minWidth="-Infinity" prefWidth="200.0"/>
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS"/>
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="30.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="40.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="25.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="25.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="25.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="25.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" prefHeight="30.0" vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<ImageView id="coverView" fx:id="coverView" fitHeight="200.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" GridPane.rowSpan="7"/>
<Label id="trackName" fx:id="trackName" maxWidth="1.7976931348623157E308" text="trackName" GridPane.columnIndex="1" GridPane.rowIndex="1">
<font>
<Font name="System Bold" size="16.0"/>
</font>
</Label>
<Label id="artist" fx:id="artist" maxWidth="1.7976931348623157E308" text="artist" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<Label id="album" fx:id="album" maxWidth="1.7976931348623157E308" text="album" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Label id="genre" fx:id="genre" maxWidth="1.7976931348623157E308" text="genre" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Label id="trackNumber" fx:id="trackNumber" maxWidth="1.7976931348623157E308" text="trackNumber" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
</children>
</fx:root>
And the Java controller/root element:
package customjavafx.scene.control;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.media.Media;
import java.io.IOException;
import java.util.Map;
public class MediaMetadataDisplay extends GridPane {
public MediaMetadataDisplay() {
final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("MediaMetadataDisplay.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
media.addListener((obs, oldVal, newVal) -> updateMedia(newVal));
}
private final ObjectProperty<Media> media = new SimpleObjectProperty<>((Media) null, "media");
@FXML private ImageView coverView;
@FXML private Label trackName;
@FXML private Label artist;
@FXML private Label album;
@FXML private Label genre;
@FXML private Label trackNumber;
public void updateMedia(Media media) {
// TODO show updated metadata
}
public ObjectProperty<Media> mediaProperty() {
return media;
}
public Media getMedia() {
return mediaProperty().get();
}
public void setMedia(final Media media) {
mediaProperty().set(media);
}
}
The cause of the error, in the stacktrace shown by Scene Builder :
Caused by: javafx.fxml.LoadException: Root value already specified.
file:/Users/guillaumegaly/Library/Application%20Support/Scene%20Builder/Library/custom-controls_2.11.jar!/customjavafx/scene/control/MediaMetadataDisplay.fxml
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2613)
at javafx.fxml.FXMLLoader.createElement(FXMLLoader.java:2771)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2720)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
at customjavafx.scene.control.MediaMetadataDisplay.<init>(MediaMetadataDisplay.java:26)
... 18 more
Remove the line
fxmlLoader.setRoot(this);
Your FXML defines the root to be an AnchorPane (and you can't set the root twice, which is why you are getting the error).
<fx:root type="javafx.scene.layout.GridPane" id="MediaMetadataDisplay" hgap="20.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0"
prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
I am really thankful to this post