Search code examples
javajavafxcontrollerstage

JavaFX how to access Controller from other stage Controller?


My app has two Stages. First Stage is the main Stage and it's launching when app starts. I have prepared for this Stage FXML files and Controllers as follows:

  1. LauncherController (parent) for Launcher.fxml, where I have a TabView with some tabs and included by fx:id FXML files for everyone tab and for TopMenuButtons.
  2. WaitingModeController (child) for WaitingMode.fxml (included into Launcher.fxml)
  3. NewConversationController (child) for NewConversation.fxml (included into Launcher.fxml)
  4. TopMenuButtonsController (child) for TopMenuButtons.fxml (included into Launcher.fxml)

LauncherController:

    public class LauncherController implements Initializable {

@FXML
private TabPane wholeTabPane;
@FXML
private Tab waitingModeTab;
@FXML
private Tab newConversationTab;

@FXML
private WaitingModeController waitingModeFXMLController;
@FXML
private NewConversationController newConversationFXMLController;

@Override
public void initialize(URL location, ResourceBundle resources) {
    TabPane tabPane = wholeTabPane;
}

public void openNewConverationTab(){
    wholeTabPane.getSelectionModel().select(newConversationTab);
}
}

I have a button in TopMenuButtons with a method openAssisstantStage(), that open new small Stage:

public class TopMenuButtonsController {

public void openAssisstantStage(ActionEvent event) {
    Stage stage = (Stage)((Button)event.getSource()).getScene().getWindow();
    stage.setIconified(true);
    FXMLLoader loadAssisstant = new FXMLLoader(this.getClass().getResource(FXMLFilePaths.ASSISSTANT_FXML));
    Parent assisstant = null;

    try {
        javafx.geometry.Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
        assisstant = (Parent) loadAssisstant.load();
        Stage waitingStage = new Stage();
        waitingStage.setTitle("Asystent");
        waitingStage.setScene(new Scene(assisstant));
        waitingStage.setAlwaysOnTop(true);
        waitingStage.initStyle(StageStyle.UNDECORATED);
        waitingStage.setX(primaryScreenBounds.getMaxX() - 500);
        waitingStage.setY(primaryScreenBounds.getMinY());
        waitingStage.show();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
}

For this stage I have also FXML file and Controller (AssisstantController). There is only 2 buttons - 1st button should open previous Stage and close current, but 2nd button also should open prevoius Stage, close current AND also open a specific tab in my TabView and do some other logic eg. get actual LocalDate:

    public class AssisstantController {

@FXML
private Button maxWindowButton;
@FXML
private Button goToNewConversationButton;

@FXML
public void openMainWindow(ActionEvent actionEvent) {
    Stage stage = (Stage) ((Button) actionEvent.getSource()).getScene().getWindow();
    stage.close();
    Launcher.getMainStage().setIconified(false);
}

@FXML
public void goToNewConversation(ActionEvent actionEvent) {
    FXMLLoader loader = new FXMLLoader(getClass().getResource(FXMLFilePaths.LAUNCHER_FXML));
    LauncherController controller = (LauncherController)loader.getController();
    controller.openNewConverationTab();

    Stage stage = (Stage) ((Button) actionEvent.getSource()).getScene().getWindow();
    stage.close();
    Launcher.getMainStage().setIconified(false);

//do other logic...
}
}

I can't initialize in LauncherController my AssisstantController, because assisstant isn't launcher's child. I can't use Mediator pattern from the same reason.

Opening previous Stage works fine, but when I try to get access to LauncherController from FXMLLoader I have a NullPointerException caused by

controller.openNewConverationTab();

in AssisstantController. I have no idea how it should work... I want to open previouse Stage and set specific tab as active.

My Launcher with main() and start() methods:

public class Launcher extends Application {

public static final String APP_NAME = "...";

public static Double AppVersion = 1.1;
private static Stage mainStage;

public static Stage getMainStage() {
    return mainStage;
}
public void openMainStage(Stage mainStage) {
    this.mainStage = mainStage;
}

@Override
public void start(Stage primaryStage) throws Exception {
    openMainStage(primaryStage);
    FXMLLoader loadMainScene = FXMLLoaderSingleton.getInstance().getLoader();
    loadMainScene.setLocation(this.getClass().getResource(FXMLFilePaths.LAUNCHER_FXML));
    Pane mainScenePane = loadMainScene.load();
    Scene mainScene = new Scene(mainScenePane);
    primaryStage.setScene(mainScene);
    primaryStage.setTitle(APP_NAME);
    primaryStage.show();
}

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

}

Solution

  • One way to do this is Create a class for example 'StageConfig'

    public class StageConfig
    {
        private static TopMenuButtonsController tmbc = null;
        private static AssisstantController ac = null;
    
        public static void setTopMenuButtonsController(TopMenuButtonsController tmbc_val)
        {
            StageConfig.tmbc = tmbc_val;
        }
        public static TopMenuButtonsController getTopMenuButtonsController() 
        {
            return StageConfig.tmbc;
        }
    
        public static void setAssisstantController(AssisstantController ac_val)
        {
            StageConfig.ac = ac_val;
        }
        public static getAssisstantController getController() 
        {
            return StageConfig.ac;
        }
    
    }
    

    Now initialize it for Example

    public class TopMenuButtonsController implements Initializable
    {
    
       @Override
       public void initialize(URL location, ResourceBundle resources) 
       {
           StageConfig.setTopMenuButtonsController(this);
       }
    
    }
    

    Now u can have controller anywhere you want use.

    StageConfig.getTopMenuButtonsController().SOMENODE.