如何从外部更新JavaFX场景?
我正在尝试学习JavaFX并将swing应用程序转换为JavaFX。 我想做的是使用JavaFX显示程序的进度 我之前在Swing中所做的是首先创建一个带有自定义JComponent的JFrame。然后让我的主程序调用自定义JComponent的一个方法,该方法将更改JComponent和repaint()中形状的颜色 下面给出了我希望在JavaFX中实现的一种方法:如何从外部更新JavaFX场景?,java,swing,javafx,Java,Swing,Javafx,我正在尝试学习JavaFX并将swing应用程序转换为JavaFX。 我想做的是使用JavaFX显示程序的进度 我之前在Swing中所做的是首先创建一个带有自定义JComponent的JFrame。然后让我的主程序调用自定义JComponent的一个方法,该方法将更改JComponent和repaint()中形状的颜色 下面给出了我希望在JavaFX中实现的一种方法: //Run JavaFX in a new thread and continue with the main program.
//Run JavaFX in a new thread and continue with the main program.
public class Test_Main{
public static void main(String[] args) {
Test test = new Test();
Thread t = new Thread(test);
t.start();
//Main Program
JOptionPane.showMessageDialog(null, "Click 'OK' to continue.",
"Pausing", JOptionPane.INFORMATION_MESSAGE);
//Update Progress
test.setText("Hello World!");
}
}
我目前将此作为我的runnable
public class Test extends Application implements Runnable{
Button btn;
@Override
public void run() {
launch();
}
@Override
public void start(Stage stage) throws Exception {
StackPane stack = new StackPane();
btn = new Button();
btn.setText("Testing");
stack.getChildren().add(btn);
Scene scene = new Scene(stack, 300, 250);
stage.setTitle("Welcome to JavaFX!");
stage.setScene(scene);
stage.show();
}
public void setText(String newText){
btn.setText(newText);
}
}
在我尝试更新按钮的文本之前,一切都正常运行,在该按钮中我获得了一个NullPointerException
。我想这与JavaFX应用程序线程有关。我在网上找不到任何描述如何从外部更新内容的东西
我看到很多关于Platform.runLater
和Task
的内容,但它们通常嵌套在start方法中,并在计时器上运行
更新:
我只是想澄清一下,我希望实现这样的目标:
public class Test_Main{
public static void main(String[] args) {
final boolean displayProgress = Boolean.parseBoolean(args[0]);
Test test = null;
if(displayProgress){ //only create JavaFX application if necessary
test = new Test();
Thread t = new Thread(test);
t.start();
}
//main program starts here
// ...
//main program occasionally updates JavaFX display
if(displayProgress){ //only update JavaFX if created
test.setText("Hello World!");
}
// ...
//main program ends here
}
}
尝试从UI线程内调用,如下所示:
public void setText(final String newText) {
Platform.runLater(new Runnable() {
@Override
public void run() {
btn.setText(newText);
}
});
}
无论何时,只要您想对UI的某个元素进行更改,都必须在UI线程中进行<代码>平台.runLater(新的Runnable())代码>就可以做到这一点。这可以防止阻塞和其他奇怪的与UI相关的错误和异常的发生
提示:您在
平台上读到的内容。runLater
在应用程序启动时被调用,而on timer通常是一种立即加载大部分UI的方法,然后在一两秒钟后填充其他部分(计时器),以便在启动时不会阻塞。但是,Platform.runLater
不仅用于启动,还可用于您需要更改/使用/与UI元素交互的任何时候。NullPointerException与线程无关(尽管您的代码中也存在线程错误)
Application.launch()
是一种静态方法。它创建应用程序
子类的实例,初始化Java FX系统,启动FX应用程序线程,并在创建的实例上调用启动(…)
,在FX应用程序线程上执行
因此,调用start(…)
的Test
实例与在main(…)
方法中创建的实例不同。因此,在Test\u Main.Main()
中创建的实例中的btn
字段永远不会初始化
如果您添加了一个只执行一些简单日志记录的构造函数:
public Test() {
Logger.getLogger("Test").log(Level.INFO, "Created Test instance");
}
您将看到创建了两个实例
API根本不是设计成这样使用的。在使用JavaFX时,应该将start(…)
本质上看作是main
方法的替代方法。(事实上,在Java8中,您可以从应用程序
子类中完全省略main
方法,并且仍然从命令行启动该类。)如果希望类可重用,请不要将其设为应用程序
的子类;要么让它成为某个容器类型节点的子类,要么(在我看来更好)给它一个访问这样一个节点的方法
代码中也存在线程问题,尽管这些问题不会导致空指针异常。作为场景图一部分的节点只能从JavaFX应用程序线程访问。Swing中也存在类似的规则:只能从AWT事件处理线程访问Swing组件,因此您确实应该在该线程上调用JOptionPane.showMessageDialog(…)
。在JavaFX中,您可以使用Platform.runLater(…)
来安排Runnable
在FX应用程序线程上运行。在Swing中,您可以使用SwingUtilities.invokeLater(…)
调度Runnable
在AWT事件调度线程上运行
混合使用Swing和JavaFX是一个非常高级的主题,因为您需要在两个线程之间进行通信。如果您希望启动一个对话框作为JavaFXStage的外部控件,那么最好将该对话框也设置为JavaFX窗口
更新:
在评论中讨论之后,我假设JOptionPane
只是一种提供延迟的机制:我将在这里修改您的示例,使其在更改按钮文本之前只需等待五秒钟
底线是,您希望以不同方式重用的任何代码都不应位于应用程序
子类中。创建一个应用程序
子类作为启动机制。(换句话说,Application
子类实际上是不可重用的;将启动过程之外的所有东西都放在其他地方。)因为您可能希望以多种方式使用调用的类Test
,所以应该将其放在POJO(普通的旧Java对象)中并创建一个方法,该方法允许访问它定义的UI部分(并钩住任何逻辑;尽管在实际应用程序中,您可能希望将逻辑分解到不同的类中):
现在让我们假设您希望以两种方式运行它。为了便于说明,我们将有一个TestApp
,它以文本“Testing”开始按钮,然后五秒钟后将其更改为“Hello World!”:
现在是一个ProductionApp
,它立即启动,文本直接初始化为“Hello World!”:
请注意,Application.launch(…)
有一种重载形式,它将Application
子类作为参数。因此,您可以在其他地方使用一个main方法来决定将执行哪个应用程序
:
import javafx.application.Application;
public class Launcher {
public static void main(String[] args) {
if (args.length == 1 && args[0].equalsIgnoreCase("test")) {
Application.launch(TestApp.class, args) ;
} else {
Application.launch(ProductionApp.class, args);
}
}
}
请注意,每次调用JVM只能调用launch(…)
一次,这意味着最好只从main
方法调用它
继续“分而治之”主题,如果你想选择r
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TestApp extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// launch app:
Test test = new Test("Testing");
primaryStage.setScene(new Scene(test.getView(), 300, 250));
primaryStage.show();
// update text in 5 seconds:
Thread thread = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException exc) {
throw new Error("Unexpected interruption", exc);
}
Platform.runLater(() -> test.setText("Hello World!"));
});
thread.setDaemon(true);
thread.start();
}
}
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ProductionApp extends Application {
@Override
public void start(Stage primaryStage) {
Test test = new Test("Hello World!");
primaryStage.setScene(new Scene(test.getView(), 300, 250));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
import javafx.application.Application;
public class Launcher {
public static void main(String[] args) {
if (args.length == 1 && args[0].equalsIgnoreCase("test")) {
Application.launch(TestApp.class, args) ;
} else {
Application.launch(ProductionApp.class, args);
}
}
}
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class DataModel {
private final StringProperty text = new SimpleStringProperty(this, "text", "");
public final StringProperty textProperty() {
return this.text;
}
public final java.lang.String getText() {
return this.textProperty().get();
}
public final void setText(final java.lang.String text) {
this.textProperty().set(text);
}
public DataModel(String text) {
setText(text);
}
}
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
public class Test {
private Pane view ;
public Test(DataModel data) {
Logger.getLogger("Test").log(Level.INFO, "Created Test instance");
view = new StackPane();
Button btn = new Button();
btn.textProperty().bind(data.textProperty());
view.getChildren().add(btn);
}
public Parent getView() {
return view ;
}
}
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TestApp extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// launch app:
DataModel data = new DataModel("Testing");
Test test = new Test(data);
primaryStage.setScene(new Scene(test.getView(), 300, 250));
primaryStage.show();
// update text in 5 seconds:
Thread thread = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException exc) {
throw new Error("Unexpected interruption", exc);
}
// Update text on FX Application Thread:
Platform.runLater(() -> data.setText("Hello World!"));
});
thread.setDaemon(true);
thread.start();
}
}
public class HeadlessApp {
public static void main(String[] args) {
DataModel data = new DataModel("Testing");
data.textProperty().addListener((obs, oldValue, newValue) ->
System.out.printf("Text changed from %s to %s %n", oldValue, newValue));
Thread thread = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException exc) {
throw new Error("Unexpected Interruption", exc);
}
data.setText("Hello World!");
});
thread.start();
}
}
package javafxtest;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* @author ericjbruno
*/
public class ShowJFXWindow {
{
// Clever way to init JavaFX once
JFXPanel fxPanel = new JFXPanel();
}
public static void main(String[] args) {
ShowJFXWindow dfx = new ShowJFXWindow();
dfx.showWindow();
}
public void showWindow() {
// JavaFX stuff needs to be done on JavaFX thread
Platform.runLater(new Runnable() {
@Override
public void run() {
openJFXWindow();
}
});
}
public void openJFXWindow() {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
Stage stage = new Stage();
stage.setTitle("Hello World!");
stage.setScene(scene);
stage.show();
}
}