3

I am learning to write FXML custom components for use with JavaFX 8 and Scene Builder.

I wrote the FXML file shown below but Scene Builder will not open it, giving me the message "Open operation has failed" due to the exception:

java.io.IOException: javafx.fxml.LoadException: mycustomcomponent.TicoTeco is not a valid type.
/C:/Users/xxxxx/Documents/NetBeansProjects/MyCustomComponent/src/mycustomcomponent/TicoTeco.fxml:9
    at com.oracle.javafx.scenebuilder.kit.fxom.FXOMLoader.load(FXOMLoader.java:92)
    at com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument.(FXOMDocument.java:80)
    at com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument.(FXOMDocument.java:95)
...

Why am I getting this exception?

Here's the FXML file:

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<fx:root type="mycustomcomponent.TicoTeco" prefHeight="93.0" prefWidth="304.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <BorderPane layoutX="61.0" prefHeight="115.0" prefWidth="200.0">
         <left>
            <Button fx:id="tico" mnemonicParsing="false" text="Tico" BorderPane.alignment="CENTER" />
         </left>
         <right>
            <Button fx:id="teco" mnemonicParsing="false" text="Teco" BorderPane.alignment="CENTER" />
         </right>
      </BorderPane>
   </children>
</fx:root>

And here are the Java files for TicoTeco.java and Main.java:

package mycustomcomponent;

import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;

public class TicoTeco extends AnchorPane {

    @FXML
    Button tico;

    @FXML
    Button teco;

    public TicoTeco() throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(TicoTeco.class.getResource("TicoTeco.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);
        fxmlLoader.load();
    }

    @FXML
    public void initialize() {
        final EventHandler<ActionEvent> onAction = 
                event -> System.out.println("Hi, I'm " + (event.getSource() == tico? "Tico" : "Teco") + "!");
        tico.setOnAction(onAction);
        teco.setOnAction(onAction);
    }
}
package mycustomcomponent;

import java.io.IOException;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws IOException {
        Scene scene = new Scene(new TicoTeco());

        primaryStage.setTitle("Here are Tico and Teco!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}
aw-think
  • 4,723
  • 2
  • 21
  • 42
user118967
  • 4,895
  • 5
  • 33
  • 54
  • 1
    Can you try adding the import statement "" in the fxml? Your code works fine in eclipse. Probably it's a NetBeans thing, since it gets the fxml from the src folder instead of the deployment folder? – Roland Jun 17 '15 at 05:16
  • 1
    did you import the jar file which contains the custom component into sceneBuilder? – griFlo Jun 17 '15 at 06:20
  • @Roland. Thanks. In fact the import statement is not needed because I used the full qualified name. The code also works with NetBeans, however it does not work with Scene Builder. For that to happen, the type attribute must be AnchorPane, and the custom component needs to be imported into Scene Builder (which is weird, since I am developing it). – user118967 Jun 17 '15 at 21:55
  • @griFlo: thanks. I had not done that. It seems counter-intuitive to have to import a custom component in other to develop it! :-) But alas, that seems to be the case. – user118967 Jun 17 '15 at 21:56

1 Answers1

5

It's a bit tricky. So your fxml have a little mistake:

Your custom class is extending AnchorPane, so this should be the root in your fxml:

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<fx:root type="AnchorPane" prefHeight="93.0" prefWidth="304.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <BorderPane layoutX="61.0" prefHeight="115.0" prefWidth="200.0">
         <left>
            <Button fx:id="tico" mnemonicParsing="false" text="Tico" BorderPane.alignment="CENTER" />
         </left>
         <right>
            <Button fx:id="teco" mnemonicParsing="false" text="Teco" BorderPane.alignment="CENTER" />
         </right>
      </BorderPane>
   </children>
</fx:root>

After that, you have to make a jar of it, because you have a fxml and a java class. This is the tricky part in Netbeans, so follow up:

First: Create an own Library Project for the component that looks like this with your copied source files:

enter image description here

Second: Delete the copied Main (where the main method is in) file

Third: Do a "Clean and Build" at the project. The generated .jar file will be in the subfolder "dist" in your Project directory.

Fourth: Open Scene Builder and import your CustomComponent .jar file like this:

enter image description here

enter image description here

Now you are able to use the component as you want. But be aware of changes to the component are not dynamicaly refresh the imported jar, you have to do the whole thing again.

aw-think
  • 4,723
  • 2
  • 21
  • 42
  • Thanks. Actually the FXML works even if the type is mycustomcomponent.TicoTeco, but Scene Builder will not import it like that. Does that mean SB only imports things derived from standard JavaFX controls, like AnchorPane? That is disappointing, for it means custom components are not really treated like standard ones. For example, one cannot then define a custom component from another custom component, then? – user118967 Jun 17 '15 at 21:51
  • So, this is a programming paradigm, you should always use supertypes where possible or interfaces. So even a sub-subcomponent is derived from AnchorPane. An really true component looks an other way, may you have a look in sources of JavaFX, there is so much more than only a class like AnchorPane. – aw-think Jun 18 '15 at 01:47