JavaFX中的多个独立阶段

时间:2015-04-25 17:41:53

标签: 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)中的绘图函数。

所以主要问题是如何“设计”PlotUtils?到目前为止,我尝试了两件事

  1. PlotUtils为每个绘图调用使用Application.launch(每次创建一个包含单个场景的新舞台) - >不起作用Application.launch只能调用一次。
  2. 在第一次调用PlotUtils期间创建某种“Main Stage”,获取对创建的Application的引用并从那里开始后续阶段 - >不能使用Application.launch(SomeClass.class)我无法获得对创建的Application实例的引用。
  3. 什么样的结构/设计可以让我实现这样的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");
    
        }
    }
    

1 个答案:

答案 0 :(得分:4)

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

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中并在PlotUtils.plot(...)处理程序中调用onSucceeded任务。

如果你真的想从非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);
    }
}