0

Ich habe ein JavaFX-Programm, das eine Aufgabe von einer separaten Klasse gestartet. Ich möchte den Status des Fortschritts dieser Aufgabe an ein Element Label in der Benutzeroberfläche ausgeben. Ich kann das nicht zur Arbeit bringen. Hier ist mein Code:JavaFX: Update UI in separaten, nicht-Controller-Klasse

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:

Das Label I aktualisieren wollen, erstellt von global @FXML public Label label = new Label(); delcaring. Erstellt einen neuen Thread über new Thread(new Task<Void>() { ... }).start();, um die collect_data-Methode von TaskRun auszuführen. Die TaskRun Klasse ist unten angegeben:

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

} 

ich eine NullPointerException auf der Linie mit dem Platform.runLater(...) bekommen.

So offensichtlich wird diese Methode nicht funktionieren. Wie aktualisiere ich die Benutzeroberfläche über diese Nicht-Controller-Klasse TaskRun?

+2

Wenn der Controller eine Instanz von Taskrun startet, können Sie Folgendes tun: new TaskRun (this) und in TaskRun's Konstruktor: TaskRun (Controller control) {ui = contro;) vielleicht versuchen, wie dies;), aber es hängt davon ab, wo Taskrun erstellt wird – azro

+0

Es ist in einem Thread innerhalb des Controllers erstellt. Vielleicht ist das das Problem? – jshapy8

+1

nicht sicher, aber sicherlich werde ich nicht arbeiten, wie du es getan hast, weil du 2 Instanzen von Controller hinzufügen wirst, es muss das gleiche sein, versuche, was ich schrieb und erzähle uns – azro

Antwort

2

Ihr Design ist nicht wirklich sehr hilfreich für das, was Sie hier versuchen. Anstatt einer Klasse, die die Aufgabe vollständig ausblendet und einen Thread erstellt, um sie auszuführen (was wirklich unflexibel ist: Was, wenn Sie die Aufgabe an eine existierende Executor übergeben wollen?), Sollte Ihre Klasse die Aufgabe implementieren. Dann können Sie nur den Fortschritt der Aufgabe aktualisieren, die eine beobachtbare Eigenschaft ist:

class TaskRun extends Task<Void> { 

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

} 

Jetzt in Ihrem Controller können Sie einfach tun:

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

Alternativ verwenden Sie eine Bindung anstelle des Hörers:

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

Dies beseitigt all die schrecklichen Kopplung, die Sie haben, denn jetzt TaskRun muss nichts über die Controller-Klasse wissen.

+0

Ich stimme zu, dieses Design ist viel sauberer. – jshapy8

1

Das Übergeben der Instanz des Controllers, der die Benutzeroberfläche aktualisiert, löst das Problem (Danke an @azro). Es stellt sich heraus, dass das Instanziieren eines Controllers mit fxmlLoader.getController() nicht die aktuelle Instanz des Controllers zurückgibt, der die Benutzeroberfläche aktualisiert.

Alles, was geändert werden muss, ist der TaskRun Konstruktor:

TaskRun(Controller c) { 
    ui = c; 
} 

Wo ui die globale Controller Objekt für TaskRun Klasse.

Die Controller-Klasse instanziiert ein TaskRun Objekt innerhalb eines Thread wie folgt: TaskRun task = new TaskRun(Controller.this);

Nachdem diese Änderungen vornehmen, um die UI-Updates wie erwartet.