gpt4 book ai didi

java - 如何从外部更新JavaFX场景?

转载 作者:行者123 更新时间:2023-11-29 07:00:53 27 4
gpt4 key购买 nike

我正在尝试学习JavaFX,并将swing应用程序转换为JavaFX。
我要做的是使用JavaFX来显示程序的进度。

我以前在Swing中所做的是首先使用自定义JComponent创建一个JFrame。然后让我的主程序调用自定义JComponent的方法,该方法将更改JComponent和repaint()中形状的颜色。

下面给出了我想在JavaFX中实现的目标的想法:

//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!");
}
}


我目前将其作为可运行对象。

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.runLaterTask的提及,但是这些通常嵌套在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
}
}

最佳答案

NullPointerException与线程无关(尽管您的代码中也存在线程错误)。

Application.launch()是静态方法。它创建Application子类的实例,初始化Java FX系统,启动FX Application Thread,并在其创建的实例上调用start(...),然后在FX Application Thread上执行。

因此,在其上调用Teststart(...)实例与您在main(...)方法中创建的实例不同。因此,您从未在btn中创建的实例中的Test_Main.main()字段被初始化。

如果添加仅执行一些简单日志记录的构造函数:

public Test() {
Logger.getLogger("Test").log(Level.INFO, "Created Test instance");
}


您将看到创建了两个实例。

该API根本不设计为以这种方式使用。使用JavaFX时,应基本上将 start(...)替换为 main方法。 (实际上,在Java 8中,您可以完全从 main子类中省略 Application方法,并且仍然可以从命令行启动该类。)如果希望某个类可重用,请不要使其成为以下子类的子类。 Application;要么使其成为某个容器类型节点的子类,要么(在我看来更好)为它提供访问此类节点的方法。

您的代码中也存在线程问题,尽管这些不会导致空指针异常。场景图中的节点只能从JavaFX Application Thread访问。 Swing中存在类似的规则:只能从AWT事件处理线程访问swing组件,因此,您实际上应该在该线程上调用 JOptionPane.showMessageDialog(...)。在JavaFX中,可以使用 Platform.runLater(...)安排 Runnable在FX Application Thread上运行。在Swing中,可以使用 SwingUtilities.invokeLater(...)安排 Runnable在AWT事件分发线程上运行。

将Swing和JavaFX混合是一个相当高级的主题,因为您必须在两个线程之间进行通信。如果您希望将对话框作为JavaFX阶段的外部控件启动,最好也将对话框设置为JavaFX窗口。

更新:

在评论中进行讨论之后,我假设 JOptionPane只是提供延迟的一种机制:我将在此处修改您的示例,以便在更改按钮的文本之前只需等待五秒钟。

最重要的是,您要以不同方式重用的任何代码都不应位于 Application子类中。仅将 Application子类创建为启动机制。 (换句话说, Application子类实际上是不可重用的;将启动过程之外的所有东西都放在其他地方。)由于您可能想以多种方式使用被称为 Test的类,因此应将其放在POJO中。 (普通的Java对象)并创建一个方法,该方法可以访问它定义的UI部分(并挂钩任何逻辑;尽管在实际应用程序中,您可能希望将逻辑分解为另一个类):

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 Button btn;
private Pane view ;

public Test(String text) {
Logger.getLogger("Test").log(Level.INFO, "Created Test instance");

view = new StackPane();
btn = new Button();
btn.setText(text);
view.getChildren().add(btn);

}

public Parent getView() {
return view ;
}

public void setText(String newText){
btn.setText(newText);
}
}


现在,假设您要运行这两种方式。为了进行说明,我们将使用 TestApp,该按钮以文本“ Testing”开始按钮,然后五秒钟后将其更改为“ Hello World!”:

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

}
}


现在,一个 ProductionApp随即直接启动,其文本直接初始化为“ Hello World!”:

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


请注意,存在 Application.launch(...)的重载形式,该形式将 Application子类作为参数。因此,您可以在其他地方使用main方法来决定要执行哪个 Application

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方法进行调用是一种好习惯。

继续以“分而治之”为主题,如果您希望选项“无头”运行应用程序(即完全没有UI),则应从UI代码中排除正在处理的数据。无论如何,在任何实际应用中,这都是一个好习惯。如果您打算在JavaFX应用程序中使用数据,则使用JavaFX属性表示数据将很有帮助。

在这个玩具示例中,唯一的数据是字符串,因此数据模型看起来非常简单:

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


修改后的 Test类封装了可重复使用的UI代码,如下所示:

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 ;
}
}


基于UI的应用程序如下所示:

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();
}

}

关于java - 如何从外部更新JavaFX场景?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26344172/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com