您如何在创建的原始 window 中的控制器之间转换

How do you transition between controllers within the original window that was created

我目前有 3 个 classes。

ScreenController(控制器class):

import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.layout.AnchorPane;
import java.net.URL;
import java.util.ResourceBundle;

public class ScreenController implements Initializable
{
    private AnchorPane window;

    public ScreenController()
    {
        super();
    }

    public ScreenController(AnchorPane window)
    {
        setWindow(window);
    }

    public void setWindow(AnchorPane window)
    {
        this.window = window;
    }

    public void setScreen(String screen)
    {
        try
        {
            Parent root = FXMLLoader.load(getClass().getResource("/com/app/client/resources/fxml/" + screen + ".fxml"));
            window.getChildren().setAll(root);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public void initialize(URL location, ResourceBundle resources)
    {
    }
}

LoginScreen(主屏幕):

import com.app.client.java.controllers.ScreenController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;

import java.io.IOException;

public class LoginScreen extends ScreenController
{
    @FXML
    private AnchorPane loginWindow;

    @FXML
    private Button goButton;

    public LoginScreen()
    {
        super();
        setWindow(loginWindow);
    }

    @FXML
    public void goButtonPressed(ActionEvent event) throws IOException
    {
        setScreen("Home");
        System.out.println("Success.");
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane fx:id="loginWindow" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" opacity="0.5" prefHeight="500.0" prefWidth="850.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.app.client.java.classes.LoginScreen">
   <children>
      <Button fx:id="goButton" layoutX="205.0" layoutY="60.0" mnemonicParsing="false" onAction="#goButtonPressed" text="Button" />
   </children>
</AnchorPane>

主屏幕(副屏):

import com.app.client.java.controllers.ScreenController;
import javafx.fxml.FXML;
import javafx.scene.layout.AnchorPane;

public class HomeScreen extends ScreenController
{
    @FXML
    private static AnchorPane homeWindow = new AnchorPane();

    public HomeScreen()
    {
        super (homeWindow);
    }
}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane fx:id="homeWindow" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.app.client.java.classes.HomeScreen">
   <children>
      <TextArea layoutX="200.0" layoutY="100.0" prefHeight="200.0" prefWidth="200.0" text="aksajkasjkasja" />
   </children>
</AnchorPane>

我希望能够使用 setScreen() 函数从主屏幕移动到辅助屏幕。但是,我发现该过程没有成功完成。

我发现另一种可行的方法是(虽然它调整了 window 的大小,而不是用新的内容填充初始 window):

Parent root = FXMLLoader.load(getClass().getResource("/com/app/client/resources/fxml/" + screen + ".fxml"));
Stage stage = (Stage) loginWindow.getScene().getWindow();
Scene scene = new Scene(root);
stage.setScene(scene);

但是,我更愿意使用初始实现,因为它更简洁、可读性更强,而且从理论上讲,它提供了我想要的确切行为。

您目前拥有的有几个问题:

  1. 在您的 LoginScreen 构造函数中,您使用尚未注入的字段的值调用 setWindow

    public LoginScreen()
    {
        super();
        setWindow(loginWindow);
    }
    

    当控制器的构造函数正在执行时,不会注入任何 FXML 字段——这意味着 loginWindownull。原因不言而喻:FXMLLoader 必须先构造控制器实例,然后才能开始注入适当的字段。

    事件的顺序是:(1) 控制器实例化,(2) 字段注入,(3) 初始化方法被调用;我相信链接任何事件 handlers/change 侦听器都包含在第二步中。这意味着任何需要发生的关于 FXML 字段的初始化都应该在 initialize 方法中完成。

    您的 HomeScreen 构造函数中存在与 super(homeWindow) 相同的问题,但还有其他问题将在下一点中解决。

  2. 除了尝试访问构造函数中尚未注入的字段外,还有以下两个问题:

    @FXML
    private static AnchorPane homeWindow = new AnchorPane();
    

    第一个问题是您初始化了一个要注入的字段。 永远不要这样做。一个好的经验法则是:如果该字段用 @FXML 注释,则不要手动为其分配值。 FXML 字段最终将被注入,这意味着您事先分配给它的任何值都将被简单地替换。这可能会导致一些细微的问题,因为任何引用先前值的代码都不会使用实际添加到场景图中的对象。

    另一个问题是您的字段是静态的。 JavaFX 8+ 不支持注入静态字段。据我所知,它在旧版本中曾经是可能的,但这种行为从未得到官方支持(即是一个实现细节)。此外,让一些固有的基于实例的东西(FXML+控制器)设置一个会影响所有实例的静态字段是没有意义的。

    一个额外的问题:当你使 homeWindow 成为非静态时,你不能再使用 super(homeWindow) 因为你不能在调用超级构造函数之前引用它。

使用两个修改后的 类 应该允许您的代码 运行:

LoginScreen.java:

public class LoginScreen extends ScreenController {

    @FXML private AnchorPane loginWindow;
    @FXML private Button goButton;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        super.initialize(location, resources);
        setWindow(loginWindow); // set window in initialize method
    }

    @FXML
    public void goButtonPressed(ActionEvent event) throws IOException {
        setScreen("Home");
        System.out.println("Success.");
    }

}

HomeScreen.java:

public class HomeScreen extends ScreenController {

    @FXML private AnchorPane homeWindow;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        super.initialize(location, resources);
        setWindow(homeWindow); // set window in initialize method
    }

}

但是不要使用:

window.getChildren().setAll(root);

在您的 ScreenController#setScreen 方法中,它会导致一个微妙的问题。您正在添加 root 作为 window 节点的子节点。但是当这种情况发生时,ScreenController 的新实例(与新的 root 关联)有它的 window == root。换句话说,用 LoginScreen 创建的 window 现在是用 HomeScreen 创建的 window 的父级。根据更复杂的应用程序的设计方式,这可能导致 "roots" 的嵌套越来越深。

也就是说,您已经有了另一种方法,您实际上可以替换整个 Scene。正如您所说,您在那里遇到的问题是 Stage 调整大小以适应新的 Scene。这可以通过替换 Sceneroot 而不是 Scene 本身来解决:

window.getScene().setRoot(root);

一些可能有用的资源:

您可以在初始化期间获取 JavaFX 应用程序的主要阶段。其他场景 类 应该有一个带有 getter 和 setter 的 Stage 字段,这样您就可以通过他们的 Controller 传递主舞台。至于 window 调整大小,您可以通过在 setScene() 语句中添加 getStage().getWidth()getStage().getHeight() 来解决这个问题。

我要指出的一个小例子:

    public class MainClass extends Application {

      @Override
      public void start(Stage stage) throws Exception {

        InputStream sceneStream = MainClass.class.getResourceAsStream("/fxml"+
        "/newScene/main.fxml");
        FXMLLoader loader = new FXMLLoader();
        Parent root = loader.load(sceneStream);
        Scene scene = new Scene(root);
        stage.setTitle("App title");

        NewScene controller = loader.getController();
        controller.setMainStage(stage);

        stage.setScene(scene, stage.getWidth(), stage.getHeight());
        stage.show();

     }

所以上面的函数是从创建主舞台的MainClass开始的。请注意中间部分与代码的其余部分有点分离,通过获取已加载 Scene 的控制器,我将阶段传递给它。您可以通过这种方式将舞台传递到所有场景。还要注意设置场景的部分,我在这里使用了两个从舞台中提取的参数;宽度和高度。除此之外,还有更多方法可以在主舞台上运行的几乎每个场景中获得舞台,只需执行以下操作:

    @FXML private Button aButton;

    public Button getAButton(){
       return aButton;
    }

    Stage stage = (Stage) getAButton().getScene().getWindow();

这将适用于所有基于初级阶段的场景,无论类型如何,只需要您在场景图表中注册Node