2017-07-20 122 views
0

所有人。 我有一個程序,它應該自動控制一些機器。我需要javaFX來顯示研討會的臨時狀態。有幾個進程一個接一個地執行,對於他們中的每一個我需要更新屏幕上的圖像(讓我們更簡單一點,說我們需要更新標籤)。從另一個線程javafx更新ImageView

所以,有一個主線程,它控制着機器,並且有一個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和對甲骨文的文檔,現在我不能弄明白所有這些綁定的,可觀察性,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> 

有連接到它的控制器。我認爲這是我們應該聽取圖像變化或其他事情的地方。

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

你應該想到的start()方法,應用程序的入口點,而不是main(...)方法的,所以你應該從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 
    } 
} 

注意這個類是完全獨立於用戶界面的。你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()方法。

1

以下是您可以嘗試的可執行示例。它遵循詹姆斯的回答中列出的一些原則,所以我不會在評論中增加額外的內容。如果您還有其他問題,請在答案下面的評論中提問。

有很多方法可以解決這個問題,這只是舉例說明了我提出的一個簡單例子(例如James答案中的消費者機制比此答案中的事件通知機制更優雅)。它可能不是您的案例的最佳結構,但希望它爲您提供一些關於如何解決您的問題的見解。

示例程序提供工廠視圖,其中工廠由四臺機器組成。每臺機器可以處於IDLE狀態或BAKING狀態,並且每臺機器的狀態都會獨立變化,工廠中的每臺機器都在其自己的線程上運行。提供了整個工廠的圖形視圖。工廠中的機器視圖列出了每臺機器的機器標識和當前機器狀態。提供了一個通知接口,以便圖形視圖可以動態感知底層機器狀態的任何變化並適當更新自身。爲了確保在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; 
    } 
}