devoured elysium
devoured elysium

Reputation: 105067

How to run one of multiple distinct stages at application startup with javafx?

I'm starting with javafx and I'm having some trouble understanding how to correctly model the following situation:

Ideally I'd like to have a main() method that would somehow allow me to either open a LoginDialog or if there's already a user/password combination available on disk, to bypass login and directly show the MainDialog to the user.

My main issue is that when I run Application.launch() I'm expected to submit an Application instance, and when implementing one, I don't have any control over its Stage object creation, which creates a catch-22 for me here.

I could create a LoginScene and MainScene but then I'd have no control for things like the Stage's title, for instance.

What's the usual route to solve this kind of issues with javafx?

Thanks

Upvotes: 0

Views: 1712

Answers (2)

James_D
James_D

Reputation: 209330

Define a single Application subclass and put the logic to decide whether you need to show the login screen in the start() method (the proper place for startup logic is the aptly-named start() method, not the main method):

public class MyApplication extends Application {

    private boolean loggedIn ;

    @Override
    public void start(Stage primaryStage) {

        loggedIn = checkLoginFromDisk();

        while (! loggedIn) {
            FXMLLoader loginLoader = new FXMLLoader(getClass().getResource("path/to/login.fxml"));
            Parent loginRoot = loginLoader.load();
            LoginController loginController = loginLoader.getController();
            Scene loginScene = new Scene(loginRoot);
            primaryStage.setScene(loginScene);
            primaryStage.setTitle("Login");
            primaryStage.showAndWait();
            // check login from controller and update loggedIn...
        }

        FXMLLoader mainLoader = new FXMLLoader(getClass().getResource("path/to/main.fxml"));
        Parent mainRoot = mainLoader.load();
        Scene mainScene = new Scene(mainRoot);
        primaryStage.setScene(mainScene);
        primaryStage.setTitle("My Application");
        primaryStage.sizeToScene();
        primaryStage.show();
    }

    private boolean checkLoginFromDisk() {
        // ... etc
    }

    // for environments not supporting direct launch of JavaFX:
    public static void main(String[] args) {
        launch(args);
    }
}

If you're not using FXML, you just define classes instead of FXML files + controllers for "login" and "main", but the structure stays the same:

public class LoginView {

    private final GridPane /* for example */ view ;

    public LoginView() {
        // setup UI, etc...
    }

    public Pane getView() {
        return view ;
    }

    public boolean checkLogin() {
        // etc...
    }
}

and

public class MainView {

    private BorderPane /* for example */ view ;

    public MainView() {
        // set up UI etc...
    }

    public Pane getView() {
        return view ;
    }
}

and your start method then looks like

@Override
public void start(Stage primaryStage) {

    loggedIn = checkLoginFromDisk();

    while (! loggedIn) {
        LoginView loginView = new LoginView();
        Scene loginScene = new Scene(loginView.getView());
        primaryStage.setScene(loginScene);
        primaryStage.setTitle("Login");
        primaryStage.showAndWait();
        loggedIn = loginView.checkLogin();
    }
    MainView mainView = new MainView();
    Scene mainScene = new Scene(mainView.getView());
    primaryStage.setScene(mainScene);
    primaryStage.setTitle("My Application");
    primaryStage.sizeToScene();
    primaryStage.show();
}

Obviously you can refactor this in many different ways (reuse the same login class or fxml instance, use a different stage for the main view, etc etc etc) as you need.

Note that there is no requirement to use the stage passed to the start() method. So if you wanted standalone classes to encapsulate the stage containing a login scene and a main scene, you could add the following classes:

public class LoginStage extends Stage {

    private final LoginView loginView ;
    public LoginStage() {
        loginView = new LoginView();
        setScene(new Scene(loginView.getView());
        setTitle("Login");
    }

    public boolean checkLogin() {
        return loginView.checkLogin(); 
    }
}

and similarly make a MainStage class. (In the FXML-based version, the LoginStage holds a reference to the LoginController and just loads the FXML in the constructor instead of instantiating the LoginView class.) Then

public class MyApplication extends Application {

    private boolean loggedIn ;

    @Override
    public void start(Stage ignored) {
        loggedIn = checkLoginFromDisk();
        while (! loggedIn) {
            LoginStage login = new LoginStage();
            loginStage.showAndWait();
            loggedIn = loginStage.checkLogin();
        }
        new MainStage().show();
    }

    // ...
} 

Upvotes: 1

devoured elysium
devoured elysium

Reputation: 105067

This seems to be remarkably similar to what I was looking for. It follows jns suggestion. Not ideal but not terrible:

class LoginScene(stage: Stage) extends Scene(new VBox()) {
  val vbox = this.getRoot.asInstanceOf[VBox]

  ...
}

class MainScene(stage: Stage) extends Scene(new VBox()) {
  val vbox = this.getRoot.asInstanceOf[VBox]

  ...
}

class ApplicationStartup extends Application {
  override def start(primaryStage: Stage): Unit = {
    val scene = if (...) new LoginScene(primaryStage) else new MainScene(primaryStage)
    primaryStage.setScene(scene)
    primaryStage.show()
  }
}

(code is presented in Scala)


Alternatively, as can be seen from the question's comments, one can just ignore the primaryStage and create our own ones at will, which it's just what I wanted from the outset:

class MainDialog extends Application {
  override def start(primaryStage: Stage): Unit = {
    val newStage = new Stage {
      setTitle("abcdef")
      setScene(new Scene(new Button("Hello World")))
    }
    newStage.show()
  }
}

Upvotes: 0

Related Questions