【问题标题】:JavaFX: update UI in separate, non-controller classJavaFX:在单独的非控制器类中更新 UI
【发布时间】:2017-05-26 21:17:35
【问题描述】:

我有一个 JavaFX 程序,它启动一个由单独的类执行的任务。我想将该任务的进度状态输出到 UI 中的 Label 元素。我不能让它工作。这是我的代码:

Main.java:

public class Main extends Application {
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("ui.fxml"));
        primaryStage.setTitle("Data collector");
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Controller.java:

我要更新的标签是通过全局删除 @FXML public Label label = new Label(); 创建的。通过new Thread(new Task<Void>() { ... }).start(); 创建一个新线程以从TaskRun 运行collect_data 方法。 TaskRun 类如下所述:

TaskRun.java:

class TaskRun {
    private Controller ui;

    TaskRun() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("ui.fxml"));
        ui = loader.getController();
    }

    void collect_data() {
        for (int i = 0; i < 100; i++) {
            // do stuff...
            send_progress_to_ui(((float) i / (float)) * 100);
        }
    }

    void send_progress_to_ui(float percent) {
        new Thread(new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                Platform.runLater(() -> ui.label.setText(Float.toString(percent_complete) + "%"));

                return null;
            }
        }).start();
    }

}

我得到一个NullPointerExceptionPlatform.runLater(...) 在线。

很明显,这种方法是行不通的。如何通过这个非控制器类TaskRun更新UI?

【问题讨论】:

  • 如果是控制器启动了Taskrun的实例,你可以这样做:new TaskRun(this),在TaskRun的构造函数中:TaskRun(Controller control){ ui = contro; ) 也许可以这样尝试;),但这取决于创建 Taskrun 的位置
  • 它是在 Controller 的一个线程中创建的。也许这就是问题所在?
  • 不确定,但我肯定不会像你那样工作,因为你会添加 2 个控制器实例,它必须相同,试试我写的并告诉我们
  • 您能否至少告诉我们什么是 null 以及为什么您希望它不为 null?不过,一般来说,对链接到您从未实际显示的 UI 的控制器执行某些操作不会取得任何成果,即使您摆脱了所有异常。
  • 单独创建 FXMLLoader 不会导致加载 fxml 并因此创建控制器。这意味着uinull

标签: java multithreading user-interface javafx nullpointerexception


【解决方案1】:

您的设计对于您在这里尝试做的事情并不是很有帮助。而不是一个完全隐藏任务并创建一个线程来运行它的类(这真的很不灵活:如果你想将任务提交给现有的Executor?),你的类应该实现这个任务。然后你可以只更新任务的进度,这是一个可观察的属性:

class TaskRun extends Task<Void> {

    @Override
    protected Void call() {
        for (int i = 0; i < 100; i++) {
            // do stuff...
            updateProgress(i, 100);
        }
        return null ;
    }

}

现在在你的控制器中你可以这样做:

TaskRun task = new TaskRun();
task.progressProperty().addListener((obs, oldProgress, newProgress) -> 
    label.setText(String.format("%.2f percent complete", newProgress.doubleValue()*100)));
new Thread(task).start();

或者,使用绑定而不是侦听器:

label.textProperty().bind(task.progressProperty().asString("%.2f percent complete"));

这消除了所有可怕的耦合,因为现在TaskRun 不需要了解控制器类的任何信息。

【讨论】:

  • 我同意,这种设计更干净。
【解决方案2】:

传递更新 UI 的控制器实例可以解决问题(感谢 @azro)。事实证明,使用fxmlLoader.getController() 实例化控制器不会返回正在更新 UI 的控制器的当前实例。

所有需要修改的是TaskRun构造函数:

TaskRun(Controller c) {
    ui = c;
}

其中uiTaskRun 类的全局Controller 对象。

控制器类在Thread 中实例化TaskRun 对象,如下所示:TaskRun task = new TaskRun(Controller.this);

进行这些更改后,UI 会按预期更新。

【讨论】:

    猜你喜欢
    • 2013-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-04
    • 2020-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多