从另一个线程javafx更新ImageView

从另一个线程javafx更新ImageView,java,multithreading,javafx,Java,Multithreading,Javafx,大家好。 我有一个程序,可以自动控制一些机器。我需要javaFX来显示研讨会的临时状态。有几个进程一个接一个地执行,对于每个进程,我都需要更新屏幕上的图像(让我们简化一下,说我们需要更新标签) 因此,有一个主线程控制机器,还有一个FX应用程序线程控制GUI public static void main(String[] args) { //some processes in the main thread before launching GUI (like connecting

大家好。 我有一个程序,可以自动控制一些机器。我需要javaFX来显示研讨会的临时状态。有几个进程一个接一个地执行,对于每个进程,我都需要更新屏幕上的图像(让我们简化一下,说我们需要更新标签)

因此,有一个主线程控制机器,还有一个FX应用程序线程控制GUI

     public static void main(String[] args) {
//some processes in the main thread before launching  GUI (like connecting to the database)
     Thread guiThread = new Thread() {
                @Override
                public void run() {
                    DisplayMain.launchGUI();
                }
            };
            guiThread.start();
//some processes after launching the GUI, including updating the image on the screen
            }
我在这里和Oracle的文档上读了一大堆资料,现在我无法理解所有这些绑定、可观察属性、Platform.runLater、任务、检索控制器、将控制器作为参数传递给某个类等等

我有一个fxml文件,假设它只显示一个标签:

    <?xml version="1.0" encoding="UTF-8"?>

    <?import javafx.scene.control.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.scene.control.Label?>


    <GridPane alignment="center" 
              hgap="10" vgap="10" 
              xmlns:fx="http://javafx.com/fxml/1" 
              xmlns="http://javafx.com/javafx/8" 
              fx:controller="sample.Controller">
       <columnConstraints>
          <ColumnConstraints />
       </columnConstraints>
       <rowConstraints>
          <RowConstraints />
       </rowConstraints>
       <children>
          <Pane prefHeight="200.0" prefWidth="200.0">
             <children>
                 <Label fx:id="label" text="Label" />
             </children>
          </Pane>
       </children>
    </GridPane>
还有一个Display.java,用作启动机制

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Display extends Application {
  @Override
  public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(loader.load(), 800, 400));
    primaryStage.show();

  }

  static void launchGUI() {
    Application.launch();
  }
}

最后,问题是:如何从main()更新控制器中的标签?关于如何在控制器之间传递数据,如何调用控制器中的方法,有很多信息,但是我完全不懂我的问题。

你应该把
start()
方法看作是应用程序的入口点,而不是
main(…)
方法,因此你应该启动其他线程(控制“机器”)从
start()
,而不是从
main()
。这样,您甚至不存在在
main()
中检索对控制器的引用的问题(这基本上是无法做到的,因为您无法获取对
应用程序
子类实例的引用)。
main()
方法只需通过调用
Application.launch()
引导JavaFX工具包的启动,而无需执行其他操作。(请注意,在某些部署场景中,甚至不会调用
main(…)
方法,而
应用程序的
子类的
start()
方法由其他机制调用。)

因此,重构如下:

public class Main { // or whatever you called it...
    public static void main(String[] args) {
        Application.launch(Display.class, args);
    }
}
然后在开始时:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Display extends Application {
  @Override
  public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(loader.load(), 800, 400));
    primaryStage.show();

    Controller controller = loader.getController();

    Thread machineryThread = new Thread(() -> {
        // some processes launching after the GUI, including updating the label
        // which you can now easily do with
        Platform.runLater(() -> controller.setLabel("Some new text"));
    });
    machineryThread.start();
  }


}

如果您想将机器与UI完全分离(这可能是个好主意),那么这样做并不难。把机器换成另一类。标签的更新实际上是使用(处理)一个
字符串的东西(对于更新图像,它可能会使用其他类型的数据)。您可以通过将其表示为
java.util.Consumer
来实现此抽象。所以你可以

public class Machinery {

    private final Consumer<String> textProcessor ;

    public Machinery(Consumer<String> textProcessor) {
        this.textProcessor = textProcessor ;
    }

    public void doMachineryWork() {
        // all the process here, and to update the label you do
        textProcessor.accept("Some new text");
        // etc etc
    }
}

根据应用程序结构的其他方面,从控制器的
initialize()
方法而不是从
start()
方法启动机器线程也可能有意义。

您应该将
start()
方法作为应用程序入口点,而不是
main(…)
方法,因此您应该从
start()
而不是从
main()
启动其他线程(控制“机器”)。这样,您甚至不存在在
main()
中检索对控制器的引用的问题(这基本上是无法做到的,因为您无法获取对
应用程序
子类实例的引用)。
main()
方法只需通过调用
Application.launch()
引导JavaFX工具包的启动,而无需执行其他操作。(请注意,在某些部署场景中,甚至不会调用
main(…)
方法,而
应用程序的
子类的
start()
方法由其他机制调用。)

因此,重构如下:

public class Main { // or whatever you called it...
    public static void main(String[] args) {
        Application.launch(Display.class, args);
    }
}
然后在开始时:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Display extends Application {
  @Override
  public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(loader.load(), 800, 400));
    primaryStage.show();

    Controller controller = loader.getController();

    Thread machineryThread = new Thread(() -> {
        // some processes launching after the GUI, including updating the label
        // which you can now easily do with
        Platform.runLater(() -> controller.setLabel("Some new text"));
    });
    machineryThread.start();
  }


}

如果您想将机器与UI完全分离(这可能是个好主意),那么这样做并不难。把机器换成另一类。标签的更新实际上是使用(处理)一个
字符串的东西(对于更新图像,它可能会使用其他类型的数据)。您可以通过将其表示为
java.util.Consumer
来实现此抽象。所以你可以

public class Machinery {

    private final Consumer<String> textProcessor ;

    public Machinery(Consumer<String> textProcessor) {
        this.textProcessor = textProcessor ;
    }

    public void doMachineryWork() {
        // all the process here, and to update the label you do
        textProcessor.accept("Some new text");
        // etc etc
    }
}

根据应用程序结构的其他方面,从控制器的
initialize()
方法而不是从
start()
方法启动机器线程也可能有意义。

这里是一个可执行示例,您可以尝试。它遵循了James回答中概述的一些原则,所以我不会在评论方面添加太多额外的内容。如果你还有其他问题,请在回答下面的评论中提问

有很多方法可以解决这个问题,这只是说明了我提出的一个快速示例(例如,James答案中的消费者机制比这个答案中的事件通知机制更优雅)。对于您的案例来说,它可能不是一个最佳结构,但希望它能为您提供一些关于如何着手解决问题的见解

示例程序提供工厂视图,其中工厂由四台机器组成。每台机器可以处于空闲状态,也可以处于烘焙状态,并且每台机器的状态都会独立改变,工厂中的每台机器都在其自己的线程上运行。提供了整个工厂的图形视图。工厂中的机器视图列出了每台机器的机器id和当前机器状态。提供了一个通知界面,以便图形视图能够动态地了解底层机器状态的任何更改,并适当地更新自身。为确保在JavaFX应用程序线程上更新图形视图,当收到机器状态更改通知事件时,用于在JavaFX应用程序线程上运行视图更新

导入javafx.application.*;
导入javafx.geometry.Insets;
导入javafx.scene.scene;
导入javafx.scene.control.Label;
导入javafx.scene.layout.*;
导入javafx.stage.stage;
导入java.util.*;
导入java.util.concurrent.*;
公共类FactoryConsole exte