2017-07-20 26 views
0

皆さん。 私はいくつかの機械を自動的に制御するプログラムを持っています。私は、ワークショップの一時的な状態を示すためにjavaFXが必要です。いくつかのプロセスが次々に実行されており、それぞれのプロセスでは、画面上のイメージを更新する必要があります(単純化し、ラベルを更新する必要があるとします)。他のスレッドからImageViewを更新するjavafx

したがって、機械を制御するメインスレッドがあり、GUIを制御するFXアプリケーションスレッドがあります。

 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、タスクのうち、意味することはできませんそれに接続されているコントローラがあり

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

:、いくつかのクラスなど

へのパラメータとして、私はFXMLファイルを持っているコントローラを渡し、のはそれだけでラベルを示しましょう。私はそれが画像や何かの変化を聞くべき場所だと考えています。

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

あなたは、アプリケーションのエントリポイント、ないmain(...)方法としてstart()方法を考えなければならない、とあなたがいないmain()から、start()から(「機械」を制御)あなたの他のスレッドを起動する必要があります。そうすれば、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(); 
    } 

だろう、それはまた、代わりにstart()方法からのコントローラのinitialize()方法から機械スレッドを起動しても意味があります。

1

ここでは、実行可能なサンプルを試すことができます。ジェームスの答えに記載されている原則のいくつかに従っているので、私はコメントの点では余分に追加しません。さらなる質問がある場合は、その答えの下のコメントで質問してください。

この問題を解決する方法はたくさんありますが、これは私が思いついた簡単な例を示しています(James AnswerのConsumerメカニズムは、この回答のイベント通知メカニズムよりもエレガントです)。あなたのケースに最適な構造ではないかもしれませんが、うまくいけば、あなたの問題を解決する方法についていくつかの洞察が得られればうれしいです。

サンプルプログラムは、工場が4台のマシンで構成されているファクトリビューを提供します。各マシンはIDLE状態またはBAKING状態のいずれかになり、各マシンの状態は独立して変化し、工場内の各マシンはそれぞれのスレッドで実行されます。工場全体をグラフィカルに表示します。ファクトリ内のマシンのビューには、各マシンのマシンIDと現在のマシン状態が表示されます。グラフィカル・ビューは、基盤となるマシンの状態の変化を動的に認識し、それ自体を適切に更新できるように、通知インターフェースが提供されています。 JavaFXアプリケーションスレッドでグラフィカルビューが更新されるようにするには、マシン状態変更通知イベントが受信されたときにJavaFXアプリケーションスレッドでビュー更新を実行するためにPlatform.runLaterが使用されます。

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; 
    } 
} 
関連する問題