Как поменять экраны в javafx-приложении в классе контроллера?

Привет, я долго искал сеть, но я не мог найти решение следующей проблемы:

В javafx вы получили 3 основных файла; класс контроллера, файл fxml и класс приложения. Теперь я хочу отреагировать на контроллер нажатием кнопки (который отлично работает) и изменить экран на этот клик (который вы обычно делаете с stage.setScreen()), но у меня нет ссылки на сцену (которую вы можно найти в классе приложения).

Приложение-образец:

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) {
    // TODO
}    
}

Я был бы благодарен за любую помощь.

Ответ 1

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

Ответ 2

Я нашел этот старый вопрос, входя в 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);
    }
}

yep, второй выглядит одинаково (некоторая логика, вероятно, может быть разделена, но текущего состояния достаточно как доказательство концепции)

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

Ответ 3

Вы также можете попробовать это.

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

}