在同一场景中加载新的FXML

43

我有2个FXML文件:

  • 布局(包括标题、菜单栏和内容)
  • AnchorPane(应该放在另一个FXML文件的内容区域内)

我想知道如何将第二个文件加载到“Master”场景的内容空间中。在JavaFX中这样做好还是加载新场景更好?

我正在尝试像这样做,但它没有起作用:

@FXML
private AnchorPane content;

@FXML
private void handleButtonAction(ActionEvent event) {        
    content = (AnchorPane) FXMLLoader.load("vista2.fxml");
}

感谢您的帮助。


你的代码不能运行是因为加载器创建了一个新的 AnchorPane,但你从未将新面板添加到场景图的父节点中。 - jewelsea
请问您能告诉我如何将新的窗格添加到场景中的父级吗?我是JavaFX的新手。 - Andre
我创建了一个答案,以提供有关FXML加载的更多细节。 - jewelsea
9个回答

80

为什么您的代码无法正常工作

加载器创建了一个新的AnchorPane,但您没有将新面板添加到场景图中的父级。

快速修复

请改为:

content = (AnchorPane) FXMLLoader.load("vista2.fxml");

编写:

content.getChildren().setAll(FXMLLoader.load("vista2.fxml"));

用新的视图替换内容的子项。 内容本身仍然在场景图中,因此当您设置其子项时,同时也将它们附加到场景图上。

您可能需要调整布局(例如使用自动调整大小的布局,如StackPanes而不是AnchorPanes)来获取所需的确切行为。

建议您不要只采用快速修复方法,而是建议您查看下面链接的简单框架,因为它可能为您提供了更通用的机制来获取您想要的行为。

参考FXML导航框架

我创建了一个小型框架,用于在主场景的一部分中交换fxml受控内容窗格。

该框架的机制与kithril的答案中提供的相同。

  1. 用于外部fxml的主面板作为子面板的持有者。
  2. 外部fxml的主控制器提供了一个公共方法,可以用于交换子面板。
  3. 方便的导航器类以主布局的主控制器静态初始化。
  4. 导航器通过调用主控制器上的方法,为主容器加载新的子面板提供了一个公共静态方法。
  5. 子面板是由其各自的fxml加载器生成的。

为什么要使用框架

该框架似乎对于回答您的问题来说有些过度,或许是这样。 但是,我发现与FXML相关的两个最常问的话题是:

  1. 在FXML生成的窗格之间导航(本问题)。
  2. 如何在FXML控制器之间传递数据

因此,我认为为这种情况提供一个小型演示框架是有必要的。

示例框架输出

第一个画面显示应用程序布局,显示第一个视图。 内容包括主应用程序布局中定义的标题和一个可互换的蓝色子内容窗格。

vista1

在下一个画面中,用户已导航到第二个视图,该视图保留主布局中的常量标题,并使用新的珊瑚色子内容窗格替换原始子项。 新的子项已从新的fxml文件中加载。

vista2

寻找更实质性的东西?

比本问题中提供的示例框架更全面且得到更好支持的轻量级框架是afterburner.fx

寻找更简单的东西?

只需交换场景根:在JavaFX中更改场景

其他选项?

动画转换和其他选项:在JavaFX中切换


你的答案看起来不错,但我无法解决这个问题。有没有针对初学者的此问题的教程? - Madan Bhandari
@jewelsea,afterburner.fx已经存档,并且指向在线购物门户网站。您能否删除您的评论以避免垃圾邮件标记 :) - Raju
@jewelsea 有点离题,但哪种做法被认为更好,是在同一场景中加载新的FXML文件还是切换不同的场景? - homerun
@someFolk 如果您正在更改场景的根节点,则无论您更改整个场景还是仅更改根节点,都不会有太大影响。如果您只更改场景的某些部分而不是整个场景,则仅为更改部分加载新的FXML文件而不是重新创建整个场景是有意义的。 - jewelsea
我知道这有点晚了!!但是当我使用FXMLloader时,我遇到了一个错误,提示找不到合适的方法。 - Sumanth Jois
显示剩余7条评论

7

我不确定这个方法有多有效,但看起来还不错,而且比上面的方法简单得多。

https://www.youtube.com/watch?v=LDVztNtJWOo

就我所理解的,这里发生的事情是这样的(与应用程序类中Start()方法中发生的非常相似):

private void buttonGoToWindow3Action(ActionEvent event) throws IOException{
    Parent window3; //we need to load the layout that we want to swap
    window3 = (StackPane)FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLWindow3"));

    Scene newScene; //then we create a new scene with our new layout
    newScene = new Scene(window3);

    Stage mainWindow; //Here is the magic. We get the reference to main Stage.
    mainWindow = (Stage)  ((Node)event.getSource()).getScene().getWindow();

    mainWindow.setScene(newScene); //here we simply set the new scene
}

然而,我不是Java专家,也很新奇编程,因此如果有经验丰富的人评估它会很好。

编辑: 我发现了一种更简单的方法:

转到MainApplication类并创建静态Stage parentWindow。

public static Stage parentWindow;
@Override
public void start(Stage stage) throws Exception {
    parentWindow = stage;

    Parent root = FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLMainScene.fxml"));

    Scene scene = new Scene(root);

    stage.setScene(scene);
    stage.show();
}

现在您可以访问主舞台,因此在程序中的任何地方,您都可以这样做来更改场景:
    Parent window1;
    window1 = FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLWindow1.fxml"));

    //Scene newSceneWindow1 = new Scene(window1);

    Stage mainStage;
    //mainStage = (Stage)  ((Node)event.getSource()).getScene().getWindow();
    mainStage = MainApplication.parentWindow;
    mainStage.getScene().setRoot(newSceneWindow1); //we dont need to change whole sceene, only set new root.

你的回答很有帮助,但是如果包括视频摘要或相关部分,你可以做得更好。这也将帮助你的回答即使链接在未来发生故障时仍然保持优秀。 - jewelsea
现在应该很清楚了。但是当加载的布局不是AnchorPane时,我无法加载布局。应用程序会抛出异常... - Tomasz Mularczyk
parentWindow设置为publicstatic是一个非常糟糕的想法。请注意,原始解决方案并没有假定窗口由MainApplication类定义(更不用说是它的主要舞台了);它根本没有参考那个类。因此,您的解决方案只适用于那种特定情况;使用原始版本更加通用。 - James_D
@James_D 你的意思是最好使用 someNode.getScene().getWindow(); 吗? - Tomasz Mularczyk
1
是的,耦合度更低了。请注意,如果您只是更改场景的根节点,则根本不需要窗口(只需执行 someNode.getScene().setRoot(...); 即可)。 - James_D
啊,是的。我忽略了它。 - Tomasz Mularczyk

0
如果你正在寻找一种方法让按钮调用新的FXML文件,这个方法对我有效。
@FXML
private void mainBClicked(ActionEvent event) throws IOException {
    Stage stage;
    Parent root;
    stage=(Stage) ((Button)(event.getSource())).getScene().getWindow();
    root = FXMLLoader.load(getClass().getResource("MainMenu.fxml"));
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
}

0
在这种情况下,我建议你使用自定义组件。首先为您的内容创建一个自定义组件:
class Content2 extends AnchorPane {
    Content() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("vista2.fxml");
        loader.setRoot(this);
        loader.setController(this);
        loader.load();
    }
}

将您的vista2.fxml文件根目录中的AnchorPane标记替换为fx:root

<fx:root type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml">
    ...
</fx:root>

然后,您可以通过使用自定义事件绑定和箭头函数来简单地完成此操作。将事件处理程序属性添加到您的Content类中:

private final ObjectProperty<EventHandler<ActionEvent>> propertyOnPreviousButtonClick = new SimpleObjectProperty<EventHandler<ActionEvent>>();

@FXML
private void onPreviousButtonClick(ActionEvent event) {
    propertyOnPreviousButtonClick.get().handle(event)
}

public void setOnPreviousButtonClick(EventHandler<ActionEvent> handler) {
    propertyOnPreviousButtonClick.set(handler);
}

最后,在您的Java代码或FXML中绑定您的自定义事件处理程序:
@FXML
onNextButtonClick() {
    Content2 content2 = new Content2();
    content2.setOnPreviousButtonClick((event) -> {
        Content1 content1 = new Content1();

        layout.getChildren().clear();
        layout.getChildren().add(content1);
    });

    layout.getChildren().clear();
    layout.getChildren().add(content2);    
}

如果您不想动态添加内容,只需将 setVisible() 设置为 truefalse 即可。

0

哇,已经超过8年了,有很多复杂的答案,却没有提及10年前的解决方案。

有一个专门用于嵌套FXML文件的fx:include元素。它不在SceneBuilder库中,因此您需要手动编写它。但是SceneBuilder可以很好地加载和显示嵌套的FXML。

<AnchorPane fx:id="content">
    <children>
        <fx:include fx:id="nestedVista" source="vista2.fxml" />
    </children>
</AnchorPane>

您可以使用fx:idfx:id+"Controller"引用嵌套的Pane和控制器。

@FXML
Pane nestedVista;

@FXML
VistaController nestedVistaController;

文档链接:https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html#nested_controllers


0

其他人可能有更好的解决方案,但我的解决方案是在外部FXML中使用简单的容器(如VBox),然后加载新内容并将其作为容器的子项添加。如果您只加载一个或两个表单,这可能是一种可行的方式。然而,对于更完整的框架,我发现这篇博客文章很有帮助:https://blogs.oracle.com/acaicedo/entry/managing_multiple_screens_in_javafx1 她提供了她的框架源代码,其中包括花哨的过渡效果。虽然它旨在管理顶级场景,但我发现很容易适应于管理内部内容区域。


0

我的掩码示例。

使用:

Main.getNavigation().load(View2.URL_FXML).Show();
Main.getNavigation().GoBack();

0

我也卡在这里了 尝试了大多数的答案,但并不是我想要的,所以我只是用给出的思路完成了这个:

public class Main extends Application {
public static Stage homeStage;
@Override
public void start(Stage primaryStage) throws Exception{
    homeStage = primaryStage;
    Parent root = FXMLLoader.load(getClass().getResource("mainView.fxml"));
    root.getStylesheets().add(getClass().getResource("stylesheet/custom.css").toExternalForm());
    homeStage.setTitle("Classification of Living Organisms");
    homeStage.setScene(new Scene(root, 600, 500));
    homeStage.show();
}


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

这是我的主类。Main.java,带有着陆窗口/页面mainView.fxml。 在我的mainController.java类中,我使用了一点@Tomasz的想法,尽管在我完成这个之前,它让我有点困惑:

public void gotoSubMenu(Event event) {
    Parent window1;
    try {
        window1 = FXMLLoader.load(getClass().getResource("src/displayView.fxml"));
        Stage window1Stage;
        Scene window1Scene = new Scene(window1, 600, 500);
        window1Stage = Main.homeStage;
        window1Stage.setScene(window1Scene);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

创建了一个名为'window1'的新父窗口,它在src目录中加载了第二个fxml文件' displayView.fxml'。 创建了一个主视图舞台对象,并将场景设置为新创建的场景,其根为window1。 希望这可以帮助那些现在进入 #JavaFX 的人。

0
保持相同的场景容器但更改场景容器内部的视图...
假设您要传递新视图和控制器到的场景容器是名为“sceneContainer”的GridPane布局。
建立一个新视图的FXMLLoader对象。
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Notifications.fxml"));

创建一个匹配的容器,将新视图的内容加载到其中。
GridPane yourNewView = fxmlLoader.load();

将新视图设置为sceneContainer。(setAll首先清除所有子项)
sceneContainer.getChildren().setAll(yourNewView);

获取新视图的控制器对象并调用启动类逻辑的方法。
Notifications notifications = fxmlLoader.getController();
notifications.passUserName(userName);

一个完整的示例看起来像这样:
@FXML
public void setNotificationsViewToScene() {

    try {
         FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Notifications.fxml"));
         GridPane yourNewView = fxmlLoader.load();
         sceneContainer.getChildren().setAll(yourNewView);

         Notifications notifications = fxmlLoader.getController();
         notifications.passUserName(userName);

    } catch (IOException e) {
         e.printStackTrace();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接