0

I have a method called changeScene() that I want to be able to call from a separate controller class. For example, when the "settings" button is pressed on the initial scene, the controller class of the fxml file for the initial scene needs to call Main.changeScene("settings.fxml").

Current code of the Main class (no errors):

public class Main extends Application {

    private Stage window;
    private Scene home, editor;

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

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

        Parent root = FXMLLoader.load(getClass().getResource("/Scenes/MainPage.fxml"));
        home = new Scene(root, 640, 400);

        window.setTitle("Home");
        window.setScene(home);
        window.show();
    }

    //Method I want to be able to call externally
    public void changeScene(String fxml) throws Exception{
        Parent pane = FXMLLoader.load(
               getClass().getResource(fxml));

       Scene scene = new Scene(pane);
       window.getScene().setRoot(pane);
    }
}

My initial thought was to simply add a constructor to the Main class in order to make it initalizeable and call the method that way, but I doubt that's the best solution. Thanks for the help in advance, I'm very knew to JavaFX.

  • You need a reference to main. One thing you could do, is create a static instance. eg. `private static Stage window;` and then you could make `changeScene` static. There are better ways though. Such as what does your Controller with a button look like? – matt Jun 01 '20 at 08:05
  • Pass an object providing the `changeScene` method to the controller of the loaded fxml, see https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml . I'd also not rely on the object type being the application class, since you may want to seperate the functionality to a seperate class; create an interface containing the `changeScene` method instead. – fabian Jun 01 '20 at 08:18
  • @matt while possible static is the worst of all options, so don't use it ;) – kleopatra Jun 01 '20 at 09:44
  • 1
    anyway, why do you want to call it from somewhere else? Smells fishy .. Also I never understood why you (and many others) want to replace the _scene_ when replacing the _root_ of the scene will have the same effect - or who wrote all assignments requiring it all over the world ;) – kleopatra Jun 01 '20 at 09:47
  • @kleopatra unless I'm doing it incorrectly which may be entirely possible, I am utilizing setRoot() in the change scene method. – Alexander Scheibe Jun 02 '20 at 02:46
  • @fabian I did end up going for a polymorphic solution (utilized an abstract class rather than and interface, but same difference). Thanks for the help. – Alexander Scheibe Jun 02 '20 at 02:55
  • ahh .. guilty of not reading beyond _new Scene(..)_ .. which is never used ;) – kleopatra Jun 02 '20 at 03:26
  • 1
    In regards to using a library, it can be good to step back a bit and look at the broader question: What are you trying to achieve? If you wanted to [Change scenes](https://stackoverflow.com/questions/12804664/how-to-swap-screens-in-a-javafx-application-in-the-controller-class/12805134#12805134) for example? – matt Jun 02 '20 at 04:55

1 Answers1

0

The best way I have seen to handle problems like this with JavaFX is to create an AbstractController class that your FXML controllers extend.

AbstractController.java

/**
 * Class for controllers to extend in order to get access to the primary stage
 */
public abstract class AbstractController
{
    /**
     * The primary stage accessible by derived classes.
     */
    protected Stage primaryStage;

    /**
     * Sets the primary stage.
     *
     * @param primaryStage the primary stage for the application
     */
    public void setPrimaryStage(Stage primaryStage)
    {
        this.primaryStage = primaryStage;
    }
}

Assuming your FXML controller class is MainController.java for MainPage.fxml, update it to extend AbstractController and then update your Main::start function like so:

        FXMLLoader loader = new FXMLLoader(getClass().getResource("/Scenes/MainPage.fxml"));
        Parent root = (Parent)loader.load();
        AbstractController controller = (AbstractController)loader.getController();
        controller.setPrimaryStage(window);

At this point, your MainController has access to the primary stage (i.e "window" as you have it named). If you move the changeScene function to AbstractController, then all controllers that extend AbstractController will have access to changeScene(). Don't forget to update changeScene() to call SetPrimaryStage for each FXML file loaded just like in Main::start

maroc
  • 466
  • 2
  • 5
  • Thank you very much. It's one of those things where if I were coding this from the ground up without the use of the JavaFX library, I probably would have thought of some sort of polymorphic solution, but being as how I'm new to utilizing dependecies like this (I'm only a junior in HS), I tend to clam up when the solution isn't black and white. – Alexander Scheibe Jun 02 '20 at 02:49
  • @kleopatra where exactly? – Alexander Scheibe Jun 02 '20 at 04:20
  • 1
    SetPrimaryStage -> setPrimaryStage. – matt Jun 02 '20 at 04:42
  • @AlexanderScheibe This of course is just one way to do it but it has worked for me. I only use it for setting the window title from the main window controller and for dealing with dialogs. I have since switched to kotlin and tornadofx which has some better conventions for dealing with things like this. – maroc Jun 02 '20 at 14:11