Frank
Frank

Reputation: 521

JavaFX 8 loading multiple fxml files into borderpane

Given the following code:

public class Main extends Application {

private BorderPane rootLayout;
private VBox toolbarLayout;

private URL path;

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

@Override
public void start(Stage stage) {                            

    FXMLLoader loader = new FXMLLoader();                

    // Root View

    path = getClass().getResource("mainLayout.fxml");
    try {
        loader.setLocation(path);            
        rootLayout = (BorderPane) loader.load();
    } catch (IOException e){
        System.out.println("Not found: " + path);
        e.printStackTrace();
    }        

    // Toolbar View
    path = getClass().getResource("toolbar/toolbarView.fxml");  
    try {                        
        toolbarLayout = (VBox) loader.load();
    } catch (IOException e){
        System.out.println("Not found: " + path);
        e.printStackTrace();
    }

    rootLayout.getChildren().add(toolbarLayout);

    Scene scene = new Scene(rootLayout);        
    stage.setScene(scene);
    stage.show();
}

If I comment out the second fxml 'try' the rootLayout loads fine. If I comment out the borderpane and set the toolbarView as the main view it works fine too. BUT if I try to load the toolbarView into the rootLayout, the rootLayout loads fine, but the toolbarView throws an exception:

javafx.fxml.LoadException: Root value already specified.

Obviously I don't understand the fxml load process well enough, so can someone please throw some light on this? Why does it think I am trying to set the root again?

For completeness, here is the toolbarView.fxml:

<VBox fx:id="idToolbar" alignment="TOP_CENTER" maxHeight="-Infinity"
 maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
  prefHeight="400.0" prefWidth="100.0" spacing="20.0" 
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">

<children>
  <Button mnemonicParsing="false" text="Button" />
  <Button mnemonicParsing="false" text="Button" />
  <Button mnemonicParsing="false" text="Button" />
</children>
  <opaqueInsets>
    <Insets />
  </opaqueInsets>
<padding>
  <Insets top="20.0" />
</padding>
</VBox>

Upvotes: 3

Views: 6321

Answers (1)

James_D
James_D

Reputation: 209225

The root property contains a reference to the structure specified by the FXML file; i.e. to the object created by the root element of the FXML file. Assuming you are not using the "dynamic root" (<fx:root>) pattern, the root will be set as part of the load process to the object corresponding to the root element of the FXML. If it is not null at this stage (i.e. if it has already been set), then you will get an exception. A similar thing is true for the controller property: if the FXML file specifies an fx:controller attribute, the controller will be set as part of the load() process; if it is not null, an exception is thrown.

The FXMLLoader is really only designed to be used once, as you have many interdependent properties which are typically set as part of the load process: root, location, controller, resources, and elements of the namespace. So you should really create a new FXMLLoader for each FXML file you want to load:

FXMLLoader loader = new FXMLLoader();                

// Root View

path = getClass().getResource("mainLayout.fxml");
try {
    loader.setLocation(path);            
    rootLayout = (BorderPane) loader.load();
} catch (IOException e){
    System.out.println("Not found: " + path);
    e.printStackTrace();
}        

// Toolbar View

loader = new FXMLLoader();

path = getClass().getResource("toolbar/toolbarView.fxml");  
try {                        

    // note you omitted this line:
    loader.setLocation(path);

    toolbarLayout = (VBox) loader.load();
} catch (IOException e){
    System.out.println("Not found: " + path);
    e.printStackTrace();
}

rootLayout.getChildren().add(toolbarLayout);

It may be possible to reuse an FXMLLoader by carefully unsetting anything that has been set as part of the previous load process:

FXMLLoader loader = new FXMLLoader();                

// Root View

path = getClass().getResource("mainLayout.fxml");
try {
    loader.setLocation(path);            
    rootLayout = (BorderPane) loader.load();
} catch (IOException e){
    System.out.println("Not found: " + path);
    e.printStackTrace();
}     

loader.setRoot(null);
loader.setController(null);
loader.setResources(null);
loader.getNamespace().clear();   

// Toolbar View
path = getClass().getResource("toolbar/toolbarView.fxml");  

try {                        

    // note you omitted this line:
    loader.setLocation(path);

    toolbarLayout = (VBox) loader.load();
} catch (IOException e){
    System.out.println("Not found: " + path);
    e.printStackTrace();
}

rootLayout.getChildren().add(toolbarLayout);

but this is really not the intended usage, and may not be robust to future changes to the FXMLLoader implementation.

Upvotes: 4

Related Questions