JavaFX Programmcode pausieren gestaltet sich als schwierig

IlmoMilmo

Mitglied
Nabend zusammen,

ich bin gerade dabei ein rundenbasiertes Spiel zu erstellen, bei denen eine Person gegen 3 Spieler (KI´s) antritt.
Jeder Zug eines Spielers wird in einer Console (wird als TextArea dargestellt) protokolliert.

Um eine Nachdenkzeit bei den KI´s zu simmulieren, möchte ich nach/vor jedem Zug ein Wartezeit einbauen.
Das pausieren funktioniert prinzipiell auch, nur dass das Schreiben in die Console nicht nach jedem Zug (Schleifendurchlauf) geschieht, sondern erst "alles" am Ende der Laufzeit geschrieben wird.

Mittlerweile weiß ich, dass nach setText()/appendText() der Text nicht direkt geschrieben/angezeit wird, sondern erst, wie ich ja auch schon sage, am Ende der Laufzeit. Ich habe mir dazu mal ein vereinfachtes Beispiel einfallen lassen um das Problem evtl. etwas nachvollziehbarer aufzuzeigen


Java:
package com.example;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TestClass extends Application {

    private int loopCounter = 0;

    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Text generieren");
        TextArea textArea = new TextArea();
        button.setOnAction(event -> {
            do {
                String newText = "Hier ist der neue generierte Text.\n";
                textArea.appendText(newText);
                     //updateUi() ???
                pauseExecution(3000);
            } while (someCondition());
        });

        // Layout
        VBox root = new VBox();
        root.getChildren().addAll(button, textArea);

        // Szenen-Erstellung und Zuweisung zum Primarstage
        Scene scene = new Scene(root, 300, 200);
        primaryStage.setScene(scene);
        primaryStage.setTitle("JavaFX TextArea Beispiel");
        primaryStage.show();
    }

    private void pauseExecution(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException ignored) {
        }
    }

    private boolean someCondition() {
        loopCounter++;
        return loopCounter < 5;
    }

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

Hier wird nach dem Buttonklick der Text nicht nach 3 Sekunden geschrieben, sondern erst am Ende. Wenn ich das Problem in dem Beispiel gelöst bekomme, müsste ich dies auch für mein Projekt abbildbar sein. Ich schätze, dass ich nach dem Schreiben des Textes irgendwie dafür sorgen muss, dass die UI-Komponente neu gerendet wird, oder liege ich da falsch?
 

KonradN

Super-Moderator
Mitarbeiter
Das Problem ist, dass Du den JavaFX Application Thread blockierst. Bitte merke Dir, dass Du NIE länger dauernde Dinge auf dem Application Thread machst!

Kurz zur Beschreibung des Problems:
Die Anwendung bearbeitet immer sogenannte Events. Ein Event ist z.B. der Click auf deinen Button. Ein anderes Event ist: "Male das Fenster / das Control neu".
Wenn Du nun in dem Button Thread irgendwas an den Controls änderst, dann wird das im Control vermerkt und das Control packt ein Event zum neu malen in die Warteschlange.
Da der JavaFX Application Thread aber durch den ButtonClick blockiert ist, werden diese Events nicht bearbeitet.

Die Lösung ist also, dass Du das, was etwas länger dauert, auf einen eigenen Thread auslagerst. Dabei ist aber wichtig: Die UI Elemente / Controls sind nicht Thread sicher. Daher sind die Änderungen dann über Platform.runLaterdurchzuführen.
 

IlmoMilmo

Mitglied
Das Problem ist, dass Du den JavaFX Application Thread blockierst. Bitte merke Dir, dass Du NIE länger dauernde Dinge auf dem Application Thread machst!

Kurz zur Beschreibung des Problems:
Die Anwendung bearbeitet immer sogenannte Events. Ein Event ist z.B. der Click auf deinen Button. Ein anderes Event ist: "Male das Fenster / das Control neu".
Wenn Du nun in dem Button Thread irgendwas an den Controls änderst, dann wird das im Control vermerkt und das Control packt ein Event zum neu malen in die Warteschlange.
Da der JavaFX Application Thread aber durch den ButtonClick blockiert ist, werden diese Events nicht bearbeitet.

Die Lösung ist also, dass Du das, was etwas länger dauert, auf einen eigenen Thread auslagerst. Dabei ist aber wichtig: Die UI Elemente / Controls sind nicht Thread sicher. Daher sind die Änderungen dann über Platform.runLaterdurchzuführen.
Danke für die schnelle Antwort.
Also um dich da richtig zu verstehen: Sämtliche Änderungen an den Controls werden durch den Application Thread durchgeführt und die eingebaute Pause auf dem gleichen Thread läuft, ist der Application Thread blockiert und somit wird die UI nicht geupdatet !? Wobei doch der Application Thread nach der Zeit x doch wieder freigegeben ist und weiter arbeiten kann? Oder ist da die Zeitspanne einfach zu kurz, da ja der Thread nach kurzer Zeit wieder blockiert ist ?

Naja ich habe es mal mit dem Platform.runLater versucht.

Hattest du dir das so in etwas vorgestellt? Merke da zumindest noch keinen Unterschied, evtl. habe ich da aber noch nen Denkfehler...

Java:
package com.example;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TestClass extends Application {

    private int loopCounter = 0;
    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Text generieren");

        TextArea textArea = new TextArea();

        button.setOnAction(event -> {
            do {
                String newText = "Hier ist der neue generierte Text.\n";
                updateTextArea(textArea, newText);
                pauseExecution(3000);
            } while (someCondition());
        });

        VBox root = new VBox();
        root.getChildren().addAll(button, textArea);

        Scene scene = new Scene(root, 300, 200);
        primaryStage.setScene(scene);
        primaryStage.setTitle("JavaFX TextArea Beispiel");
        primaryStage.show();
    }

    private boolean someCondition() {
        loopCounter++;
        return loopCounter < 5;
    }

    private void updateTextArea(TextArea textArea, String newText) {
        new Thread(() -> Platform.runLater(() -> textArea.appendText(newText))).start();
    }

    private void pauseExecution(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException ignored) {
        }
    }

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

KonradN

Super-Moderator
Mitarbeiter
Nein,
Das, was du in setOnAction hast, das muss in einem neuen Thread laufen.

Das updateTextArea hat kein newThread aber das Platform.invokeLater ist da richtig.
 

IlmoMilmo

Mitglied
Danke erstmal für die Bemühungen.
Könntest du dies evtl. durch ein kleines Beispiel deutlich machen? So wirklich will das bei mir noch nicht...
Entweder kriege in den gesamten Text direkt am Anfang geschmissen oder alles am Ende :D
 

KonradN

Super-Moderator
Mitarbeiter
Einfach einmal der angepasste Code, was ich meinte:
Java:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TestClass extends Application {

    private int loopCounter = 0;
    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Text generieren");

        TextArea textArea = new TextArea();

        button.setOnAction(event -> {
            new Thread( () -> {
                do {
                    String newText = "Hier ist der neue generierte Text.\n";
                    updateTextArea(textArea, newText);
                    pauseExecution(3000);
                } while (someCondition());
            }).start();
        });

        VBox root = new VBox();
        root.getChildren().addAll(button, textArea);

        Scene scene = new Scene(root, 300, 200);
        primaryStage.setScene(scene);
        primaryStage.setTitle("JavaFX TextArea Beispiel");
        primaryStage.show();
    }

    private boolean someCondition() {
        loopCounter++;
        return loopCounter < 5;
    }

    private void updateTextArea(TextArea textArea, String newText) {
        Platform.runLater(() -> textArea.appendText(newText));
    }

    private void pauseExecution(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException ignored) {
        }
    }

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

Es wird also ein Thread gestartet, wenn der Button gedrückt wird. Und beim Hinzufügen von Text wird Platform.runLater verwendet.
 

IlmoMilmo

Mitglied
Okay, dann war ich ja garnicht so weit weg. Ich hatte den Thread nur innerhalb von do-while gestartet und nicht außerhalb, das war wohl der Fehler.
Dann schaue ich mal, wie ich das entsprechend in meinem Projekt einbauen kann! Danke :)
 

Neue Themen


Oben