将 Javafx 8 GUI 与剩余代码分离

Separating Javafx 8 GUI from remaining code

阅读javafx 8 教程时,这似乎是主要工作流程:

public class Test extends Application{
    public static void main(String[] args){
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader fxmlLoader = new FXMLLoader(TestFXController.class.getResource("test.fxml"));
        Parent root;
        try {
            root = fxmlLoader.load();
            Scene scene = new Scene(root, 1200, 800);
            primaryStage.setScene(scene);
            primaryStage.show();
            TestFXController controller = fxmlLoader.getController();
            controller.plotSomething();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

假设我有一个想要运行 的算法。启动上述应用程序后,我可能会得到一个包含 "run algorithm" 按钮的界面。按下按钮后,动作处理程序调用算法。 然后我有:启动 java 应用程序 -> 构建界面 -> 按下按钮解决算法 -> 显示解决方案。将图形内容与算法分开的只是一个按钮。实际上,图形界面'drives' 应用程序在某种意义上说它负责启动算法。 然而,我更喜欢这样的东西:

public class Test2{
    public void main(String[] args){
        Algorithm alg=new Algorithm();
        alg.solve();
        GUI gui =new GUI(); //Spawns a Javafx 8 Graphical User Interface
        gui.displaySolution(alg.getSolution());
    }
}

对我来说,这似乎更干净了?但是我不确定如何用 javafx 8 做到这一点,或者这是否可能?非常感谢任何示例或参考。我应该在 GUI class 中输入什么,以便启动 javafx 8 界面? Test2 中的示例也将打开使用像这样的干净的观察者设计模式的可能性:

public class Test3{
    public void main(String[] args){
        Algorithm alg=new Algorithm();
        alg.addListener(new GUI()); //Add a Javafx 8 GUI as a listener.
        alg.addListener(new TextualLogger());
        alg.solve();
    }
}

请注意,在 classes Test2 和 Test3 中,GUI 不再驱动应用程序。

为了澄清,我的主要问题是:如果我 运行 Test2 中的代码,GUI class 的实现应该是什么?像这样:

public class GUI extends Application{

  public GUI(){
    //What should I put here? Perhaps launch(new String[]); ?
  }

  @Override
  public void start(Stage primaryStage) throws Exception {
    FXMLLoader fxmlLoader = new FXMLLoader(TestFXController.class.getResource("test.fxml"));
    Parent root;
    try {
    root = fxmlLoader.load();
    Scene scene = new Scene(root, 1200, 800);
    primaryStage.setScene(scene);
    primaryStage.show();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void displaySolution(Solution sol){
    ...
  }

}

我不确定我是否完全按照您的尝试进行操作 - 您是否正在尝试打开第二个 JavaFX Window?

这样的东西行得通吗?

Scene resultScene = algorithm.getSolution();
Stage resultStage = new Stage();
resultStage.setScene(resultScene);
resultStage.addEventHandler() or addEventFilter()
resultStage.show();

这个阶段可以是它自己的 window 也可以是 primaryStage 的子阶段,这样如果您关闭父阶段,它也会关闭。

在 JavaFX 应用程序中,您应该将 start(...) 方法本质上等同于 "regular" Java 中的 main(...) 方法应用。 (事实上​​ ,在 Java 8 中,JavaFX 应用程序根本不需要 main(...) 方法。)这种启动 JavaFX 应用程序的机制是按顺序引入的尽可能强制程序员在正确的线程上初始化 UI(与 Swing 相比,Swing 发布了大量错误启动 GUI 的代码)。为方便起见,start(...) 方法已通过初始阶段,但如果您更喜欢使用其他方法,则无需使用它。

所以你可以这样做

public class Test2 extends Application {

    @Override
    public void start(Stage primaryStage) {
        Algorithm alg = new Algorithm();
        alg.solve();
        GUI gui = new GUI();
        gui.displaySolution(alg.getSolution());
    }

    // included for the benefit of IDEs that do not support
    // launching an Application without a main method:
    public static void main(String[] args) { launch(args); }
}

现在 GUI 不是 Application 子类(这是有道理的,因为它代表 GUI,而不是应用程序):

public class GUI {

  public GUI(){

    FXMLLoader fxmlLoader = new FXMLLoader(TestFXController.class.getResource("test.fxml"));
    Parent root;
    try {
        root = fxmlLoader.load();
        Scene scene = new Scene(root, 1200, 800);
        Stage stage = new Stage();
        stage.setScene(scene);
        stage.show();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void displaySolution(Solution sol){
    ...
  }

}

这里要记住的一件事是 start(...) 在 FX 应用程序线程上执行。因此,一旦您显示了 UI,任何需要执行的其他长 运行 进程都应该在后台线程上执行。在您描述的用例中,所有繁重的工作都在显示 UI 之前完成,因此这不是问题,但如果您尝试扩展此模式,则可能需要考虑这一点。

如果您真的想从一个已经 运行 的 JavaFX window 打开第二个。您可以访问。

Launch JavaFX application from another class

它将解决您的问题。

下面是如何 运行 新建 JavaFx 应用程序的代码

        Platform.runLater(new Runnable(){
            @Override
            public void run(){
                new MainApp().start(new Stage()); // MainApp is the class name of your second Application
            }
        });

使您的 class 扩展应用程序并实现可运行并在其中添加下面提到的代码 class

@Override
public void run(){
    launch();
}

launch() 方法将从 运行() 方法调用。不要在第二个 class 中使用 Main() 方法,否则它会抛出异常。