1

I have 2 fxml file. Example A.fxml. B.fxml. I have 2 controller. AController (A.fxml) BController (B.fxml). A fxml and B fxml have change button changing Scene or fxml. This is button code;

try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/infoLibrary/view/A.fxml"));
        Parent root = loader.load();
        Scene scene = new Scene(root);
        Stage stage = (Stage)((Node)event.getSource()).getScene().getWindow();
        stage.setScene(scene);
        stage.show();
    } catch (IOException e) {
        e.printStackTrace();
    }

Same code in BContrroller change button. When i click change button scane changing. But everytime init method and controller contructers working too. When user change fxml javafx everytime using new constructor. How can i change windows without new controller constructor?

Volkan Okçu
  • 132
  • 3
  • 14

1 Answers1

1

Use a view model:

public class ViewModel {

    public enum View {A, B}

    private final ObjectProperty<View> currentView = new SimpleObjectProperty<>(View.A);

    public ObjectProperty<View> currentViewProperty() {
        return currentView ;
    }

    public final View getCurrentView() {
        return currentViewProperty().get();
    }

    public final View setCurrentView(View view) {
        currentViewProperty().set(view);
    }

}

Now in your AController do:

public class AController {

    private ViewModel viewModel ;

    public void setViewModel(ViewModel viewModel) {
        this.viewModel = viewModel ;
    }

    // button handler:
    @FXML
    private void goToB(ActionEvent event) {
        viewModel.setCurrentView(ViewModel.View.B);
    }
}

and BController is similar.

Finally, you set everything up with something like the following, which is executed only once (e.g. in your start() method, or somewhere similar):

Stage stage = ... ; // maybe it's the primary stage in start...
Scene scene = new Scene();
ViewModel viewModel = new ViewModel();

FXMLLoader aLoader = new FXMLLoader(getClass().getResource("/infoLibrary/view/A.fxml"));
Parent a = aLoader.load();
AController aController = aLoader.getController();
aController.setViewModel(viewModel);

FXMLLoader bLoader = new FXMLLoader(getClass().getResource("/infoLibrary/view/B.fxml"));
Parent b = bLoader.load();
BController bController = bLoader.getController();
bController.setViewModel(viewModel);

scene.rootProperty().bind(Bindings.createObjectBinding(() -> {
    if (viewModel.getCurrentView() == ViewModel.View.A) {
        return a ;
    } else if (viewModel.getCurrentView() == ViewModel.View.B) {
        return b ;
    } else {
        return null ;
    }
}, viewModel.currentViewProperty());

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

Now the two FXML files are only loaded once (so the controllers are only created once, and their initialize() methods called only once). The switching is managed by changing the state of the ViewModel and observing that state, so the scene's root is changed when the model state changes.

Here is a complete SSCCE:

ViewModel.java:

package sceneswitcher;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public class ViewModel {

    public enum View {A, B}

    private final ObjectProperty<View> currentView = new SimpleObjectProperty<>(View.A);

    public ObjectProperty<View> currentViewProperty() {
        return currentView ;
    }

    public final View getCurrentView() {
        return currentViewProperty().get();
    }

    public final void setCurrentView(View view) {
        currentViewProperty().set(view);
    }

}

AController.java:

package sceneswitcher;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class AController {

    private ViewModel viewModel ;

    @FXML
    private TextField textField ;

    public void setViewModel(ViewModel viewModel) {
        this.viewModel = viewModel ;
    }

    // button handler:
    @FXML
    private void goToB(ActionEvent event) {
        viewModel.setCurrentView(ViewModel.View.B);
    }

    public String getText() {
        return textField.getText();
    }
}

A.fxml:

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

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Button?>

<VBox fx:controller="sceneswitcher.AController" spacing="5" alignment="CENTER" 
        xmlns:fx="http://javafx.com/fxml/1">

    <Label text='This is view A'/>
    <TextField fx:id="textField" />
    <Button onAction="#goToB" text="Go to view B"/>
</VBox>

BController.java:

package sceneswitcher;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;

public class BController {
    private ViewModel viewModel ;

    @FXML
    private TextArea textArea ;

    public void setViewModel(ViewModel viewModel) {
        this.viewModel = viewModel ;
    }

    // button handler:
    @FXML
    private void goToA(ActionEvent event) {
        viewModel.setCurrentView(ViewModel.View.A);
    }

    public String getText() {
        return textArea.getText();
    }
}

B.fxml:

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

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.Button?>

<VBox fx:controller="sceneswitcher.BController" spacing="5" alignment="CENTER"
         xmlns:fx="http://javafx.com/fxml/1">

    <Label text="This is view B"/>
    <TextArea fx:id="textArea" />
    <Button onAction="#goToA" text="Go to View A"/>
</VBox>

Main.java:

package sceneswitcher;

import java.io.IOException;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws IOException {
        ViewModel viewModel = new ViewModel();

        FXMLLoader aLoader = new FXMLLoader(getClass().getResource("A.fxml"));
        Parent a = aLoader.load();
        AController aController = aLoader.getController();
        aController.setViewModel(viewModel);

        FXMLLoader bLoader = new FXMLLoader(getClass().getResource("B.fxml"));
        Parent b = bLoader.load();
        BController bController = bLoader.getController();
        bController.setViewModel(viewModel);

        Scene scene = new Scene(a, 400, 400);

        scene.rootProperty().bind(Bindings.createObjectBinding(() -> {
            if (viewModel.getCurrentView() == ViewModel.View.A) {
                return a ;
            } else if (viewModel.getCurrentView() == ViewModel.View.B) {
                return b ;
            } else {
                return null ;
            }
        }, viewModel.currentViewProperty()));

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

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322