JavaFX 8 将多个 fxml 文件加载到 borderpane

JavaFX 8 loading multiple fxml files into borderpane

给定以下代码:

public class 主要扩展应用程序 {

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();
}

如果我注释掉第二个 fxml 'try' rootLayout 加载正常。如果我注释掉 borderpane 并将 toolbarView 设置为主视图,它也可以正常工作。 但是如果我尝试将 toolbarView 加载到 rootLayout 中,rootLayout 加载正常,但 toolbarView 抛出异常:

javafx.fxml.LoadException: Root value already specified.

显然我不太了解 fxml 加载过程,所以有人可以解释一下吗?为什么它认为我正在尝试再次设置 root?

为了完整起见,这里是 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>

root 属性 包含对 FXML 文件指定结构的引用;即由 FXML 文件的根元素创建的对象。假设您没有使用 "dynamic root" (<fx:root>) pattern,root 将作为 load 过程的一部分设置为对应于 FXML 根元素的对象。如果在这个阶段它不是 null (即如果它已经被设置),那么你会得到一个异常。 controller 属性 也是如此:如果 FXML 文件指定了 fx:controller 属性,控制器将被设置为 load() 进程的一部分;如果不是null,则抛出异常。

FXMLLoader 实际上只设计为使用一次,因为您有许多相互依赖的属性,这些属性通常设置为加载过程的一部分:rootlocationcontrollerresourcesnamespace 的元素。所以你真的应该为每个你想加载的 FXML 文件创建一个新的 FXMLLoader:

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);

通过仔细取消设置作为先前加载过程的一部分设置的任何内容,可以重用 FXMLLoader

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);

但这确实不是预期的用途,并且可能对 FXMLLoader 实现的未来更改不稳健。