SceneBuilder cannot create instance of custom component with given set of properties

Viewed 466

I created this custom component:

public class IconButton extends Button {

    @FXML private ImageView imageView;

    private IconButtonState state;
    private String fullIconUrl;
    private String outlineIconUrl;

    public IconButton(@NamedArg("fullIconUrl") String fullIconUrl,
                      @NamedArg("outlineIconUrl") String outlineIconUrl) {
        URL url = getClass().getResource(View.ICON_BUTTON.getFileName());
        FXMLLoader loader = new FXMLLoader(url);

        loader.setRoot(this);
        loader.setController(this);

        state = IconButtonState.NOT_INITIALIZED;

        this.fullIconUrl = fullIconUrl;
        this.outlineIconUrl = outlineIconUrl;

        try {
            loader.load();
        } catch (IOException exception) {
            exception.printStackTrace();
            throw new RuntimeException(exception);
        }
    }

    @FXML
    public void initialize() {
        this.state = IconButtonState.ACTIVE;

        String url = buildUrl(fullIconUrl);
        Image image = new Image(url);
        imageView.setImage(image);
    }
}
<!-- icon-button.fxml -->
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>

<fx:root mnemonicParsing="false" prefHeight="65.0" prefWidth="98.0" style="-fx-background-color: transparent;" type="Button" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
   <graphic>
      <ImageView fx:id="imageView" fitHeight="64.0" fitWidth="48.0" pickOnBounds="true" preserveRatio="true">
      </ImageView>
   </graphic>
</fx:root>

Then, I instantiated my IconButton component in another fxml file like this:

<!-- home.fxml -->
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<?import agill.deshopp.components.*?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="768.0" prefWidth="1024.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
    <top>
        <HBox styleClass="app-bar" stylesheets="@../css/home.css">
         <padding>
            <Insets bottom="15.0" top="15.0" />
         </padding>
         <children>
             <IconButton fullIconUrl="form-full" outlineIconUrl="form-outline"></IconButton>
             <IconButton fullIconUrl="message-full" outlineIconUrl="message-outline"></IconButton>
             <IconButton fullIconUrl="chart-full" outlineIconUrl="chart-outline"></IconButton>
             <Region HBox.hgrow="ALWAYS"></Region>
             <IconButton fullIconUrl="settings-full" outlineIconUrl="settings-outline"></IconButton>
         </children>
        </HBox>
    </top>
   <center>
      <Pane BorderPane.alignment="CENTER" />
   </center>
</BorderPane>

The code runs fine and the screen renders as expected. However, I can't open the file in SceneBuilder. It prompts me with this exception:

java.lang.RuntimeException: Cannot create instance of agill.deshopp.components.IconButton with given set of properties: [fullIconUrl, outlineIconUrl]

javafx.fxml.LoadException: 
/home/allan/IdeaProjects/california/src/main/resources/agill/deshopp/fxml/home.fxml:14

How do I fix this?

2 Answers

The code posted in the question is not mre and can not be invoked.
However the following code is an mre. It runs with no exceptions.
Fxml files can be edited using ScreenBuilder.
Modify it to your needs to find out what's wrong in the code posted in the question.
Note that form-full throws exception so I used form_full and that the fxml assignment should include $ sign: fullIconUrl="$form_full".

package fx_tests.test;
import java.io.IOException;
import java.net.URL;
import javafx.beans.NamedArg;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

public class IconButton extends Button {

    @FXML private ImageView imageView;
    private final String fullIconUrl;

    public IconButton(@NamedArg("fullIconUrl") String fullIconUrl) {

        this.fullIconUrl = fullIconUrl;

        URL url = getClass().getResource("icon-button.fxml");
        FXMLLoader loader = new FXMLLoader(url);
        loader.setRoot(this);
        loader.setController(this);
    
        try {
            loader.load();
        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }

    @FXML
    public void initialize() {
        Image image = new Image(fullIconUrl);
        imageView.setImage(image);
    }
}

Home.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<?import fx_tests.test.*?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="168.0" 
prefWidth="124.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
    <top>
         <HBox>
         <padding>
            <Insets bottom="15.0" top="15.0" />
         </padding>
         <children>
             <IconButton fullIconUrl="$form_full"></IconButton>
         </children>
        </HBox>
    </top>
   <center>
      <Pane BorderPane.alignment="CENTER" />
   </center>
</BorderPane>

icon-button.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>

<fx:root mnemonicParsing="false" prefHeight="65.0" prefWidth="98.0" style="-fx-background-color: transparent;" 
type="Button" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
   <graphic>
      <ImageView fx:id="imageView" fitHeight="64.0" fitWidth="48.0" pickOnBounds="true" preserveRatio="true">
      </ImageView>
   </graphic>
</fx:root>

Test with:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
    
public class Main extends Application {

    private static final String IMAGE = "https://www.shareicon.net/data/128x128/2015/03/28/14104_animal_256x256.png";
    @Override
    public void start(Stage currentStage) throws Exception {

        FXMLLoader loader = new FXMLLoader(getClass().getResource("Home.fxml"));
        loader.getNamespace().put("form_full", IMAGE);
        Parent root=loader.load();
        currentStage.setScene(new Scene(root));
        currentStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

When you create a custom component in JavaFX, SceneBuilder need to know the definition of this component so it can load it. I have a few custom components of my own and had to export them as a jar, and import them into SceneBuilder. Here is an answer that gives instructions on how to do this:

Adding a custom component to SceneBuilder 2.0

One catch. SceneBuilder only supports up to Java 11. So your custom component must be built and exported to a jar with this version. I had problems with Java 14 and had to backport my specific JavaFX components to Java 11. Good luck!

Related