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
?到目前为止,我尝试了两件事
- PlotUtils 对每个情节调用使用 Application.launch(每次都创建一个只有一个场景的新舞台)--> 不起作用,因为
Application.launch
只能调用一次。
- 在第一次调用 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);
}
}
有没有办法在 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
?到目前为止,我尝试了两件事
- PlotUtils 对每个情节调用使用 Application.launch(每次都创建一个只有一个场景的新舞台)--> 不起作用,因为
Application.launch
只能调用一次。 - 在第一次调用 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);
}
}