JavaFX 中的多个独立阶段

Multiple independent stages in JavaFX

有没有办法在 JavaFX 中启动多个独立阶段?我所说的独立是指所有阶段都是从主线程创建的。

目前我的应用程序或多或少是一种算法,我想在执行过程中绘制一些图表和表格(主要是为了检查结果是否正确/调试)。

问题是我不知道如何独立创建和显示多个阶段,即我想做这样的事情

public static void main(){
    double[] x = subfunction.dosomething();
    PlotUtil.plot(x); //creates a new window and shows some chart/table etc.
    double[] y = subfunction.dosomethingelse();
    PlotUtil.plot(y); //creates a new window and shows some chart/table etc.
    .....
}

这将允许使用 PlotUtil 就像使用其他脚本语言(如 Matlab 或 R)中的绘图函数一样。

所以主要的问题是如何"design"PlotUtils?到目前为止,我尝试了两件事

  1. PlotUtils 对每个情节调用使用 Application.launch(每次都创建一个只有一个场景的新舞台)--> 不起作用,因为 Application.launch 只能调用一次。
  2. 在第一次调用 PlotUtils 期间创建某种类型的 "Main Stage",获取对创建的应用程序的引用并从那里开始后续阶段 --> 不起作用,因为使用 Application.launch(SomeClass.class) 我不是能够获得对创建的应用程序实例的引用。

哪种 structure/design 可以让我实现这样的 PlotUtils 函数?

更新 1:

我想到了下面的想法,想知道这个解决方案是否有重大错误。

接口将由所有 "Plots"

实现
public abstract class QPMApplication implements StageCreator {
   @Override
   public abstract  Stage createStage();
}

绘图功能:

public class PlotStage {
    public static boolean toolkitInialized = false;

    public static void plotStage(String title, QPMApplication stageCreator) {
        if (!toolkitInialized) {
            Thread appThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    Application.launch(InitApp.class);
                }
            });
            appThread.start();
        }

        while (!toolkitInialized) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                Stage stage = stageCreator.createStage();
                stage.show();
            }
        });
    }

    public static class InitApp extends Application {
        @Override
        public void start(final Stage primaryStage) {
            toolkitInialized = true;
        }
    }
}

使用它:

public class PlotStageTest {

    public static void main(String[] args) {

        QPMApplication qpm1 = new QPMApplication() {
            @Override
            public Stage createStage() {
                Stage stage = new Stage();
                StackPane root = new StackPane();
                Label label1 = new Label("Label1");
                root.getChildren().add(label1);
                Scene scene = new Scene(root, 300, 300);
                stage.setTitle("First Stage");
                stage.setScene(scene);
                return stage;
            }
        };

        PlotStage.plotStage(qpm1);

        QPMApplication qpm2 = new QPMApplication() {
            @Override
            public Stage createStage() {
                Stage stage = new Stage();
                StackPane root = new StackPane();
                Label label1 = new Label("Label2");
                root.getChildren().add(label1);
                Scene scene = new Scene(root, 300, 200);
                stage.setTitle("Second Stage");
                stage.setScene(scene);
                return stage;
            }
        };

        PlotStage.plotStage(qpm2);

        System.out.println("Done");

    }
}

这里最简单的方法就是重构您的应用程序,使其由 FX 应用程序线程驱动。例如,您可以将原始代码块重写为

public class Main extends Application {

    @Override
    public void start(Stage primaryStageIgnored) {
        double[] x = subfunction.dosomething();
        PlotUtil.plot(x); //creates a new window and shows some chart/table etc.
        double[] y = subfunction.dosomethingelse();
        PlotUtil.plot(y); //creates a new window and shows some chart/table etc.
        //  .....
    }

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

现在 PlotUtil.plot(...) 只是创建一个 Stage,在其中放入一个 Scene,然后 show() 就可以了。

这假定您正在调用的方法不会阻塞,但如果它们阻塞了,您只需将它们包装在 Task 中并在 onSucceeded 处理程序中调用 PlotUtils.plot(...)为了任务。

如果您真的想从非 JavaFX 应用程序中驱动它,可以通过创建一个新的 JFXPanel 来强制启动尚未启动的 JavaFX 应用程序线程。应在 AWT 事件分派线程上创建 JFXPanel

这是第二种技术的一个非常基本的例子。启动应用程序并在控制台中键入 "show"。 (输入 "exit" 退出。)

import java.util.Scanner;
import java.util.concurrent.FutureTask;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import javax.swing.SwingUtilities;


public class Main {

    private JFXPanel jfxPanel ;

    public void run() throws Exception {
        boolean done = false ;
        try (Scanner scanner = new Scanner(System.in)) {
            while (! done) {
                System.out.println("Waiting for command...");
                String command = scanner.nextLine();
                System.out.println("Got command: "+command);
                switch (command.toLowerCase()) {
                case "exit": 
                    done = true;
                    break ;
                case "show":
                    showWindow();
                    break;
                default:
                    System.out.println("Unknown command: commands are \"show\" or \"exit\"");   
                }
            }
            Platform.exit();
        }
    }

    private void showWindow() throws Exception {
        ensureFXApplicationThreadRunning();
        Platform.runLater(this::_showWindow);
    }

    private void _showWindow() {
        Stage stage = new Stage();
        Button button = new Button("OK");
        button.setOnAction(e -> stage.hide());
        Scene scene = new Scene(new StackPane(button), 350, 75);
        stage.setScene(scene);
        stage.show();
        stage.toFront();
    }

    private void ensureFXApplicationThreadRunning() throws Exception {

        if (jfxPanel != null) return ;

        FutureTask<JFXPanel> fxThreadStarter = new FutureTask<>(() -> {
            return new JFXPanel();
        });
        SwingUtilities.invokeLater(fxThreadStarter);
        jfxPanel = fxThreadStarter.get();
    }

    public static void main(String[] args) throws Exception {
        Platform.setImplicitExit(false);
        System.out.println("Starting Main....");
        new Main().run();
    }

}

如果我希望用户通过 OS 终端(即使用 System.in)进行交互,我实际上会遵循以下内容。这使用第一种技术,其中应用程序由 FX Application 子类驱动。在这里,我创建了两个后台线程,一个用于从 System.in 读取命令,一个用于处理它们,通过 BlockingQueue 传递它们。即使主 FX 应用程序线程中没有显示任何内容,阻止该线程等待命令仍然是一个非常糟糕的主意。虽然线程增加了一点点复杂性,但这避免了“JFXPanel”hack,并且不依赖于存在 AWT 实现。

import java.util.Scanner;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;


public class FXDriver extends Application {

    BlockingQueue<String> commands ;
    ExecutorService exec ;

    @Override
    public void start(Stage primaryStage) throws Exception {

        exec = Executors.newCachedThreadPool(runnable -> {
            Thread t = new Thread(runnable);
            t.setDaemon(true);
            return t ;
        });

        commands = new LinkedBlockingQueue<>();

        Callable<Void> commandReadThread = () -> {
            try (Scanner scanner = new Scanner(System.in)) {
                while (true) {
                    System.out.print("Enter command: ");
                    commands.put(scanner.nextLine());
                }
            } 
        };

        Callable<Void> commandProcessingThread = () -> {
            while (true) {
                processCommand(commands.take());
            }
        };

        Platform.setImplicitExit(false);
        exec.submit(commandReadThread);
        exec.submit(commandProcessingThread);
    }

    private void processCommand(String command) {
        switch (command.toLowerCase()) {
        case "exit": 
            Platform.exit();
            break ;
        case "show":
            Platform.runLater(this::showWindow);
            break;
        default:
            System.out.println("Unknown command: commands are \"show\" or \"exit\"");   
        }
    }

    @Override
    public void stop() {
        exec.shutdown();
    }

    private void showWindow() {
        Stage stage = new Stage();
        Button button = new Button("OK");
        button.setOnAction(e -> stage.hide());
        Scene scene = new Scene(new StackPane(button), 350, 75);
        stage.setScene(scene);
        stage.show();
        stage.toFront();
    }

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