Reputation: 105067
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
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
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