如何 运行 使用 javafx 启动应用程序时的多个不同阶段之一?

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

我刚开始使用 javafx,但在理解如何正确建模以下情况时遇到了一些问题:

理想情况下,我希望有一个 main() 方法可以让我打开一个 LoginDialog 或者如果磁盘上已经有一个 user/password 组合,绕过登录并直接向用户显示MainDialog

我的主要问题是,当我 运行 Application.launch() 我应该提交一个 Application 实例,而在实施一个实例时,我无法控制它Stageobject创作,在这里为我创造了一个catch-22。

我可以创建 LoginSceneMainScene,但是我无法控制诸如 Stage 的标题之类的东西。

用 javafx 解决此类问题的通常途径是什么?

谢谢

定义一个单独的 Application subclass 并将决定是否需要显示登录屏幕的逻辑放在 start() 方法中(启动逻辑的正确位置是恰当命名的 start() 方法,而不是 main 方法):

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);
    }
}

如果您不使用 FXML,您只需定义 classes 而不是 FXML 文件 + "login" 和 "main" 的控制器,但结构保持不变:

public class LoginView {

    private final GridPane /* for example */ view ;

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

    public Pane getView() {
        return view ;
    }

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

public class MainView {

    private BorderPane /* for example */ view ;

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

    public Pane getView() {
        return view ;
    }
}

然后您的启动方法看起来像

@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();
}

显然,您可以根据需要以多种不同的方式重构它(重复使用相同的登录 class 或 fxml 实例,为主视图使用不同的阶段,等等)。

请注意,不需要使用传递给 start() 方法的阶段。所以如果你想要独立的 classes 来封装包含登录场景和主场景的舞台,你可以添加以下 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(); 
    }
}

并类似地制作 MainStage class。 (在基于 FXML 的版本中,LoginStage 持有对 LoginController 的引用,并且只是在构造函数中加载 FXML 而不是实例化 LoginView class。)然后

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();
    }

    // ...
} 

这似乎与我要找的非常相似。它遵循jns的建议。 不理想但不可怕:

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()
  }
}

(代码以 Scala 呈现)


或者,从问题的评论中可以看出,可以忽略 primaryStage 并随意创建我们自己的,这正是我从一开始就想要的:

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()
  }
}