【问题标题】:Multiple independent stages in JavaFXJavaFX 中的多个独立阶段
【发布时间】:2015-04-25 17:41:52
【问题描述】:

有没有办法在 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 期间创建某种“主阶段”,获取对创建的应用程序的引用并从那里开始后续阶段 --> 无法使用 Application.launch(SomeClass.class) 我无法获得参考到创建的应用程序实例。

什么样的结构/设计可以让我实现这样的 PlotUtils 功能?

更新 1:

我想出了以下想法,想知道这个解决方案是否存在任何重大错误。

所有“绘图”要实现的接口

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

    }
}

【问题讨论】:

  • 只需创建多个Stage 实例并在它们上调用show()
  • 感谢您的反馈。但我仍然必须确保 1. 工具包已初始化,以及 2. 阶段是在 fx 线程(而不是主线程)上创建的。我相应地更新了帖子,非常感谢您提供进一步的建议。
  • 我想我不了解您的整个应用程序生命周期。如果我从头开始编写 R 接口,我的应用程序 start(...) 方法将显示一个控制台并在初级阶段显示它。当用户输入命令时,它将处理命令(在事件处理程序中)并输出到同一个控制台,或者(例如,如果需要显示图形),创建一个新的Stage 来显示图形.你能这样构造它吗?如果是这样,那就很简单了。
  • 您当然是对的,但我的用例略有不同。首先,也许最重要的是,我不希望控制流由 JavaFX 应用程序启动/驱动。我宁愿寻找的是一种快速(希望不是太脏)的方式来绘制一些数字,而不是(就像你在 R/Matlab 脚本中在调试/开发期间绘制一些中间结果一样)而不必担心Toolkit beeing 初始化,确保在 JavaFX 线程上绘图等等......无论如何,我真的很感激 cmets。
  • 我很难理解为什么您不使用 JavaFX 应用程序来驱动它(即使该应用程序没有做任何其他事情):毕竟,您必须使用一些东西来驱动它。但是,如果您真的不能这样做,那么强制 JavaFX 应用程序启动(如果尚未启动)的“快速破解”方法是在 AWT 事件调度线程上创建一个JFXPanel

标签: javafx


【解决方案1】:

这里最简单的方法是重构您的应用程序,使其由 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()s 它。

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

如果你真的想从非 JavaFX 应用程序中驱动它,有一个相当知名的 hack 来强制 JavaFX 应用程序线程在它尚未启动的情况下启动,方法是创建一个新的JFXPanel。应该在 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”黑客攻击,并且不依赖于存在 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);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多