Kiper
Kiper

Reputation: 339

Why when I create a tab with scenebuilder it says not a node?

@Override
public void start(Stage primaryStage) {
    this.primaryStage = primaryStage;
    this.primaryStage.setTitle("AddressApp");

    initRootLayout();

    showTabOverview();
}

/**
 * Initializes the root layout.
 */
public void initRootLayout() {
    try {
        // Load root layout from fxml file.
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(Main.class.getResource("rootLayout.fxml"));
        rootLayout = (BorderPane) loader.load();

        // Show the scene containing the root layout.
        Scene scene = new Scene(rootLayout);
        primaryStage.setScene(scene);
        primaryStage.show();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 * Shows the Tab overview inside the root layout.
 */
public void showTabOverview() {
    try {
        // Load person overview.
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(Main.class.getResource("tabLayout.fxml"));
        TabPane TabOverview = (TabPane) loader.load();

        // Set person overview into the center of root layout.
        rootLayout.setCenter(TabOverview);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

It does work with my FXML of the tabpane.

<TabPane xmlns:fx="http://www.w3.org/1999/XSL/Transform">
<tabs>
    <Tab text="Untitled Tab 1">
    </Tab>
    <Tab text="Untitled Tab 2">
    </Tab>
</tabs>
</TabPane>

But I'm trying to add per tab FXML with this method I found on this link

<Tab text="Untitled Tab 1">
        <content>
            <fx:include fx:id="fooTabPage" source="fooTabPage.fxml"/>
        </content>
    </Tab>

When I add the include with the new source for the tab it gives me IllegalArgumentException: Unable to coerce javafx.scene.control.Tab@3885406e to class javafx.scene.Node.

How do I make those tabs actually being a node? is there specific way to create them?

Upvotes: 2

Views: 1405

Answers (1)

James_D
James_D

Reputation: 209724

A Tab is not a subclass of Node, as you can see from the JavaDocs. The reason for this is that the authors of JavaFX wanted tabs to be placed only in TabPanes: if Tab were a subclass of Node then you could do, for example, HBox anyContainer = new HBox(new Tab(...));, which wouldn't really make sense (and I suspect the authors of JavaFX didn't want to code Tab defensively to deal with these scenarios. So Tab is a subclass of Object and TabPane has an ObservableList<Tab> which you can access with getTabs().

The FXMLLoader works by using (lots and lots of) reflection. This allows it to create objects on the fly whose classes are determined by the fxml file, which of course the compiler never sees. Consequently there is no compile-time checking of the type of the object created from the FXML file by the FXMLLoader. So the price you pay for the flexibility of the FXMLLoader is a loss of type safety; it's up to you as a programmer to make sure the correct types are defined in the FXML files for their intended use.

So if you have FXML code as follows:

<Tab text="Untitled Tab 1">
    <content>
        <fx:include fx:id="fooTabPage" source="fooTabPage.fxml"/>
    </content>
</Tab>

This, loosely, translates to

Tab tab = new Tab("Untitled Tab 1");
tab.setContent(FXMLLoader.load(getClass().getResource("fooTabPage.fxml")));

Since setContent is expected a Node, this will only succeed if the root element of fooTabPage.fxml refers to a Node.

If, instead, the root element of fooTabPage.fxml is a Tab, then you need the <fx:include> to occur in a place where a Tab would be expected. So you could have fooTabPage.fxml as

<?xml version="1.0" encoding="UTF-8"?>
<!-- imports .... -->
<Tab fx:controller="..." xmlns:fx="http://javafx.com/fxml/1">
    <content>
        <!-- define content here... -->
    </content>
</Tab>

and then do

<TabPane>
    <tabs>
        <fx:include source="fooTabPane.fxml" />
    </tabs>
</TabPane>

However, your fooTabPage.fxml file will probably be more reusable if you don't assume its hierarchy is going to be placed into tab pane (though this level of design is really up to you). So it might be more useful to make the root element of fooTabPage.fxml a Node instance, e.g.

<?xml version="1.0" encoding="UTF-8"?>
<!-- imports .... -->
<BorderPane fx:controller="..." xmlns:fx="http://javafx.com/fxml/1">
    <!-- define content here... -->
</BorderPane>

and do (as in the link you provided):

<TabPane>
    <tabs>
        <Tab title="...">
            <content>
                <fx:include source="fooTabPane.fxml" />
            </content>
        </Tab>
    </tabs>
</TabPane>

Upvotes: 3

Related Questions