从另一个线程javafx更新ImageView

时间:2017-07-20 00:38:51

标签: java multithreading javafx

大家。 我有一个程序,它应该自动控制一些机器。我需要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
            }

我已经在SO和Oracle的文档中阅读了大量材料,现在我无法理解所有这些绑定,可观察属性,Platform.runLater,Tasks ,检索控制器,将控制器作为参数传递给某个类等

我有一个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>

附有一个控制器。我认为这是我们应该倾听图像变化的地方。

package sample;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;

import java.net.URL;
import java.util.ResourceBundle;

public class Controller implements Initializable {

  @FXML
  public void initialize(URL location, ResourceBundle resources) {
    //some listeners?
  }

  @FXML
  private Label label;

  public void setlabel(String s) {
    label.setText(s);
  }
}

还有一个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()更新控制器中的标签?有很多信息如何在控制器之间传递数据,如何调用控制器中的方法,但我完全迷失了我的问题。

2 个答案:

答案 0 :(得分:2)

您应该将start()方法视为应用程序入口点,而不是main(...)方法,因此您应该启动其他线程(控制&#34;机器&#34;)来自start(),而非来自main()。这样你甚至不会在main()中找到检索控制器的引用的问题(你基本上不能这样做,因为你无法获得对Application子类的引用实例)。 main()方法应该通过调用Application.launch()来简单地引导JavaFX工具包的启动,而不执行任何其他操作。 (请注意,在某些部署方案中,甚至不会调用main(...)方法,并且其他机制会调用Application子类的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分开(这可能是一个好主意),那么这并不难。把机器放在另一个班级。标签的更新实际上是消耗(处理)String的东西(并且为了更新图像,它可能消耗一些其他类型的数据)。您可以通过将其表示为java.util.Consumer<String>来实现此抽象。所以你可以做到

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

请注意,此类完全独立于UI。您的start(..)方法现在将

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

    Machinery machinery = new Machinery(text ->
        Platform.runLater(() -> controller.setLabel(text)));

    Thread machineryThread = new Thread(machinery::doMachineryWork);
    machineryThread.start();
  }

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

答案 1 :(得分:1)

以下是您可以尝试的可执行示例。它遵循詹姆斯答案中概述的一些原则,因此我不会在评论方面增加太多额外内容。如果您还有其他问题,请在答案下方的评论中询问。

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

示例程序提供工厂视图,其中工厂由四台机器组成。每台机器可以处于IDLE状态或BAKING状态,每台机器的状态都会独立变化,工厂中的每台机器都在自己的线程上运行。提供整个工厂的图形视图。工厂中的机器视图列出了每台机器的机器ID和当前机器状态。提供了通知界面,以便图形视图可以动态地识别底层机器状态的任何变化并适当地更新自身。为确保在JavaFX应用程序线程上更新图形视图,Platform.runLater用于在收到机器状态更改通知事件时在JavaFX应用程序线程上运行视图更新。

factory

import javafx.application.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.util.*;
import java.util.concurrent.*;

public class FactoryConsole extends Application {
    private Factory factory = new Factory();

    @Override
    public void start(Stage stage) throws Exception {
        VBox layout = new VBox(10);
        layout.setPadding(new Insets(10));
        layout.setPrefSize(100, 100);

        for (Machine machine: factory.getMachines()) {
            MachineView machineView = new MachineView(machine);
            layout.getChildren().add(machineView);
        }

        factory.start();

        stage.setScene(new Scene(layout));
        stage.show();
    }

    @Override
    public void stop() throws Exception {
        factory.stop();
    }

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

enum MachineState {
    IDLE, BAKING
}

class MachineStateChangeEvent {
    private final Machine machine;
    private final MachineState machineState;

    public MachineStateChangeEvent(Machine machine, MachineState machineState) {
        this.machine = machine;
        this.machineState = machineState;
    }

    public Machine getMachine() {
        return machine;
    }

    public MachineState getMachineState() {
        return machineState;
    }
}

interface MachineStateListener {
    void notifyStateChange(MachineStateChangeEvent machineState);
}

class MachineView extends HBox implements MachineStateListener {
    private final Machine machine;
    private final Label label;

    public MachineView(Machine machine) {
        super();
        this.label = new Label();
        this.machine = machine;
        machine.setMachineStateListener(this);

        getChildren().add(label);
    }

    @Override
    public void notifyStateChange(MachineStateChangeEvent event) {
        if (event.getMachine() != machine) {
            return;
        }

        if (!Platform.isFxApplicationThread()) {
            Platform.runLater(() -> updateState(event.getMachineState()));
        } else {
            updateState(event.getMachineState());
        }
    }

    private void updateState(MachineState machineState) {
        label.setText(machine.getId() + ": " + machineState.toString());
    }
}

class Factory {
    private static final int N_MACHINES = 4;
    private ExecutorService pool = Executors.newFixedThreadPool(N_MACHINES);
    private List<Machine> machines = new ArrayList<>();

    public Factory() {
        for (int i = 0; i < N_MACHINES; i++) {
            machines.add(new Machine());
        }
    }

    public void start() {
        for (Machine machine: machines) {
            pool.submit(machine);
        }
    }

    public void stop() {
        // Disable new tasks from being submitted
        pool.shutdown();
        try {
            // Wait a while for existing tasks to terminate
            if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
                pool.shutdownNow();
                // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if (!pool.awaitTermination(5, TimeUnit.SECONDS))
                    System.err.println("Pool did not terminate");
            }
        } catch (InterruptedException ie) {
            // (Re-)Cancel if current thread also interrupted
            pool.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
    }

    public List<Machine> getMachines() {
        return machines;
    }
}

class Machine implements Runnable {
    private static final Random random = new Random();
    private static int nextMachineId = 1;

    private int id = nextMachineId++;

    private MachineState state = MachineState.IDLE;
    private MachineStateListener stateListener;

    public void setMachineStateListener(MachineStateListener stateListener) {
        this.stateListener = stateListener;
    }

    @Override
    public void run() {
        try {
            updateState(MachineState.IDLE);
            while (true) {
                Thread.sleep(1000 * random.nextInt(2));
                updateState(MachineState.BAKING);
                Thread.sleep(1000 * random.nextInt(3));
                updateState(MachineState.IDLE);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    private void updateState(MachineState state) {
        this.state = state;
        if (stateListener != null) {
            stateListener.notifyStateChange(new MachineStateChangeEvent(this, state));
        }
    }

    public int getId() {
        return id;
    }

    public MachineState getState() {
        return state;
    }
}