【问题标题】:Loading new fxml in the same scene在同一场景中加载新的 fxml
【发布时间】:2013-09-08 06:55:17
【问题描述】:

我有 2 个 fxml 文件:

  • 布局(标题、菜单栏和内容)
  • Anchorpane(应该放在其他 fxml 文件的内容中)

我想知道如何从“主”场景加载内容空间内的第二个文件。在 javaFX 中工作是一件好事还是加载新场景更好?

我正在尝试做这样的事情,但它不起作用:

@FXML
private AnchorPane content;

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

感谢您的帮助。

【问题讨论】:

  • 您的代码不起作用,因为加载程序创建了一个新的 AnchorPane,但您从未将新窗格添加到场景图中的父级。
  • 您能告诉我如何将新窗格添加到场景中的父级吗?我是使用 JavaFX 的新手
  • 我创建了一个答案以提供有关 fxml 加载的更多详细信息。

标签: javafx fxml scene


【解决方案1】:

保持相同的场景容器,但改变场景容器内的视图...

假设您要向其中传递新视图和控制器的场景容器是名为 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();
    }
}

【讨论】:

    【解决方案2】:

    如果您正在寻找一种方法让按钮调用新的 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();
    }
    

    【讨论】:

      【解决方案3】:

      也陷入了困境 尝试了大多数答案,这不是我想要的,所以我只是使用给出的理想来做到这一点:

      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。 使用了一点@Tomasz 的想法,虽然在我在 mainController.java 类中这样做之前让我很困惑:

      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 目录中加载了名为“displayView.fxml”的第二个 fxml 文件。 创建主视图舞台的对象并将场景设置为新创建的场景,其根为window1。 希望这对现在进入#JavaFX 的人有所帮助。

      【讨论】:

        【解决方案4】:

        为什么您的代码不起作用

        加载程序会创建一个新的 AnchorPane,但您永远不会将新窗格添加到场景图中的父级。

        快速修复

        代替:

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

        写:

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

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

        您可能需要使用布局(例如,使用 StackPanes 而不是 AnchorPanes 等自动调整布局)以获得您想要的确切行为。

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

        参考 FXML 导航框架

        我创建了一个small framework 用于在主场景的一部分中交换 fxml 控制的内容窗格。

        框架的机制与 kithril 的回答中建议的相同。

        1. 外部 fxml 的主窗格充当子窗格的支架。
        2. 外部 fxml 的主控制器提供了一个公共方法,可用于交换子窗格。
        3. 便利导航器类使用外部布局的主控制器静态初始化。
        4. 导航器提供了一个公共静态方法来将新的子窗格加载到主容器中(通过调用主控制器上的方法)。
        5. 子窗格由它们各自的 fxml 加载器在导航器中生成。

        为什么是框架

        对于回答您的问题,该框架似乎有点矫枉过正,也许确实如此。但是,我发现与 FXML 相关的两个最热门的话题是:

        1. FXML 生成的窗格之间的导航(这个问题)。
        2. How to pass data between FXML controllers

        所以我觉得这个案例需要一个小的演示框架。

        示例框架输出

        第一个屏幕显示显示第一个视图的应用程序布局。内容是在主应用程序布局中定义的标题和 aliceblue 色的可互换子内容窗格。

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

        寻找更实质性的东西?

        一个比这个问题的示例框架更广泛和更好支持的轻量级框架是afterburner.fx

        寻找更简单的东西?

        只需换掉场景根目录:Changing Scenes in JavaFX

        其他选项?

        动画过渡和其他:Switch between panes in JavaFX

        【讨论】:

        • @jewelsea - 非常感谢这个迷你框架将新的 fxml 加载到同一场景。我已经在我的小应用程序中实现了这个,但是如何在这个框架中引入模型、视图和控制器交互?您有任何指针或代码示例吗?非常感谢。
        • 您的答案看起来不错,但我无法解决此问题。有针对初学者的这个问题的 ant 教程吗?
        • @jewelsea,afterburner.fx 已存档,指向在线购物门户。你可以删除你的评论以避免垃圾邮件标志:)
        • @Raju 感谢您的评论,我删除了评论并在答案中放置了指向 afterburner.fx 的链接(当前重新定位的真实网站不是购物门户)。
        • @jewelsea 有点跑题了,但什么被认为是更好的做法,在同一场景中加载新的 fxml 或切换不同的场景?
        【解决方案5】:

        在这种情况下,我建议您改用custom component。首先为您的内容创建一个自定义组件:

        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

        【讨论】:

          【解决方案6】:

          mask 的示例。

          使用:

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

          【讨论】:

            【解决方案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.
            

            【讨论】:

            • 您的回答很有帮助,但you can make it better 包含您链接到的视频的摘要或相关部分。即使您包含的链接将来断开,这也将有助于您的答案保持良好状态。
            • 现在应该很清楚了。但是,当与 AnchorPane 不同时,我无法加载布局。应用程序抛出异常......
            • 制作parentWindow publicstatic 是一个非常糟糕的主意。请注意,原始解决方案并未假定窗口是由 MainApplication 类定义的(更不用说是它的主要阶段了);它根本没有提到那个类。因此,您的解决方案仅适用于一种特定情况;使用原始版本更加通用。
            • @James_D 你的意思是someNode.getScene().getWindow(); 更好?
            • 是的。耦合较少。请注意,如果您只是更改场景的根,则根本不需要窗口(只需执行someNode.getScene().setRoot(...);
            【解决方案8】:

            其他人可能有更好的解决方案,但我的解决方案是在外部 fxml 中有一个像 VBox 这样的简单容器,然后加载新内容并将其添加为容器的子项。如果您只加载一个或两个表单,这可能是要走的路。但是,对于更完整的框架,我发现这篇博文很有帮助:https://blogs.oracle.com/acaicedo/entry/managing_multiple_screens_in_javafx1 她有她的框架的源代码,其中包括花哨的转换。虽然它旨在管理顶级场景,但我发现它也很容易适应管理内部内容区域。

            【讨论】:

              猜你喜欢
              • 2019-07-14
              • 1970-01-01
              • 2019-06-24
              • 1970-01-01
              • 2012-04-23
              • 1970-01-01
              • 2015-09-08
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多