如何在JavaFX应用程序的控制器类中交换屏幕?

27
如果在JavaFX项目中有3个文件;一个FXML文件,一个用于FXML的控制器和一个应用程序类;那么控制器如何响应按钮点击并更改屏幕(通常使用stage.setScreen()实现)?我没有对舞台的引用(这是传递给应用程序类的start(Stage))。
应用示例:
public class JavaFXApplication4 extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("Sample.fxml"));

        Scene scene = new Scene(root);

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

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}

FXML示例:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="200.0" prefWidth="320.0"  xmlns:fx="http://javafx.com/fxml" fx:controller="javafxapplication4.SampleController">
  <children>
  <Button id="button" fx:id="nextScreen" layoutX="126.0" layoutY="90.0" onAction="#handleButtonAction" text="Next Screen" />
  <Label fx:id="label" layoutX="126.0" layoutY="120.0" minHeight="16.0" minWidth="69.0" />
  </children>
</AnchorPane>

控制器示例:

public class SampleController implements Initializable {

    @FXML
    private Label label;

    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");

        // Here I want to swap the screen!
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // ...
    }    
}
3个回答

43
@FXML
private void handleButtonAction(ActionEvent event) {
    System.out.println("You clicked me!");
    label.setText("Hello World!");
    //Here I want to swap the screen!

    Stage stageTheEventSourceNodeBelongs = (Stage) ((Node)event.getSource()).getScene().getWindow();
    // OR
    Stage stageTheLabelBelongs = (Stage) label.getScene().getWindow();
    // these two of them return the same stage
    // Swap screen
    stage.setScene(new Scene(new Pane()));
}

非常有帮助!谢谢 :) - Darshan Bidkar
当您在结尾处键入“stage.setScene(...)”时,“stage”必须为“stageTheEventSourceNodeBelongs”或“stageTheLabelBelongs”中的一个,对吗? - Panagiss

19

我在学习Java并试图解决同样的问题时,发现了这个旧问题。由于我希望场景记住切换之间的内容,所以我不能使用已接受的答案,因为在场景之间切换时会重新实例化它们(丢失其先前状态)。

无论如何,已接受的答案和相似问题的答案给了我提示,如何在不丢失状态的情况下切换场景。 主要思路是将场景的实例注入到另一个控制器中,这样控制器就不需要反复实例化新的场景,而可以使用已经存在的实例(及其状态)。

以下是实例化场景的主类:

public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        // getting loader and a pane for the first scene. 
        // loader will then give a possibility to get related controller
        FXMLLoader firstPaneLoader = new FXMLLoader(getClass().getResource("firstLayout.fxml"));
        Parent firstPane = firstPaneLoader.load();
        Scene firstScene = new Scene(firstPane, 300, 275);

        // getting loader and a pane for the second scene
        FXMLLoader secondPageLoader = new FXMLLoader(getClass().getResource("secondLayout.fxml"));
        Parent secondPane = secondPageLoader.load();
        Scene secondScene = new Scene(secondPane, 300, 275);

        // injecting second scene into the controller of the first scene
        FirstController firstPaneController = (FirstController) firstPaneLoader.getController();
        firstPaneController.setSecondScene(secondScene);

        // injecting first scene into the controller of the second scene
        SecondController secondPaneController = (SecondController) secondPageLoader.getController();
        secondPaneController.setFirstScene(firstScene);

        primaryStage.setTitle("Switching scenes");
        primaryStage.setScene(firstScene);
        primaryStage.show();
    }
}

以下是两个控制器:

public class FirstController {

    private Scene secondScene;

    public void setSecondScene(Scene scene) {
        secondScene = scene;
    }

    public void openSecondScene(ActionEvent actionEvent) {
        Stage primaryStage = (Stage)((Node)actionEvent.getSource()).getScene().getWindow();
        primaryStage.setScene(secondScene);
    }
}

是的,第二个看起来一样(可能可以共享一些逻辑,但目前的状态已经足够作为概念验证)

public class SecondController {

    private Scene firstScene;

    public void setFirstScene(Scene scene) {
        firstScene = scene;
    }

    public void openFirstScene(ActionEvent actionEvent) {    
        Stage primaryStage = (Stage)((Node)actionEvent.getSource()).getScene().getWindow();
        primaryStage.setScene(firstScene);
    }
}

5
哦,我的天啊,这真是救了我的命!我在网上搜寻了很久才找到这个东西,因为其他的例子每次都要从文件中加载资源,但我需要保留场景对象,所以非常感谢你! - SleekPanther
1
正是我所需要的!一旦解释清楚,似乎显而易见。谢谢! - Alex

2
你也可以尝试像这样做。
public void onBtnClick(ActionEvent event) {
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("login.fxml"));
        Stage stage = (Stage) btn.getScene().getWindow();
        Scene scene = new Scene(loader.load());
        stage.setScene(scene);
    }catch (IOException io){
        io.printStackTrace();
    }

}

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