Podmiana tekstu w pętli przy użyciu wątku

0

Witajcie.
Uczę się podstaw Javy (w tym momencie przerabiam wątki). Chciałem napisać najprostszą appkę w JavieFX która by je wykorzystywała.

Założenia były następujące:
*Tworzę pętlę o określonej liczbie powtórzeń
*Tworzę przycisk START, po naciśnięciu którego zawartość pola Label jest w w/w pętli podmieniana na numer iteracji tejże pętli co sekundę
*Tworzę przycisk STOP, po naciśnięciu którego zawartość pola Label jest podmieniana na napis "Koniec."
Jak już zapewne się domyślacie, po próbie zmiany zawartości pola Label przy pomocy textLabel.setText("Mój nowy napis") w pętli - wszystko kończy się freez'em całości, lub w przypadku użycia wątku wyjątkiem IllegalStateException.

Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4

Szukałem dosyć dużo w internecie, ale efekty był raczej mizerne. Odnoszę wrażenie, że dokumentacja JavyFX jest w tym wypadku strasznie uboga, a SWING wypada na tym polu znacznie lepiej.
Wiem już, że zmiany GUI nie mogą być dokonywane w innych wątkach.

Pomijając pełno różnych tutoriali szukałem również pomocy tutaj:
http://docs.oracle.com/javafx/2/api/javafx/application/Platform.html#runLater%28java.lang.Runnable%29
http://docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html
jednak za dużo mi to nie dało.

Jak rozwiązać ten problem? Podejrzewam, że kwestia jest bardzo prosta, ale nie mam pojęcia jak się za to wziąć. Trzeba wątek GUI skomunikować jakoś z wątkiem w którym jest pętla?
Z racji, że nie miałem nigdy wcześniej z czymś takim kontaktu proszę Was o pomoc.

Z góry dziękuję i pozdrawiam :)

Mój kod:
Main.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent parent = FXMLLoader.load(getClass().getResource("sample.fxml"));
        Scene scene = new Scene(parent);
        primaryStage.setTitle("MT Test");
        primaryStage.setScene(scene);
        primaryStage.show();
    }


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

Core.java

import javafx.scene.control.Label;

public class Core implements Runnable{

    private Label textLabel;

    public Core(Label textLabel) {
        this.textLabel = textLabel;
    }

    public void changeLabel(){
        for(int i = 0; i < 100; i++) {
            try {
                //No i tutaj jest mój problem
                //-----------------------------------
                //String tmp = Integer.toString(i + 1);
                //textLabel.setText(tmp);
                //-----------------------------------
                System.out.println(i + 1 + " Thread number: " + Thread.currentThread().getId());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        changeLabel();
    }
} 

Controller.java

import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;

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

public class Controller implements Initializable{

    @FXML
    private Label textLabel;

    @FXML
    private Button startButton;

    @FXML
    private Button stopButton;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        startButton.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                Core core = new Core(textLabel);
                Thread t = new Thread(core);
                t.start();
            }
        });

        stopButton.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                textLabel.setText("Koniec.");
            }
        });
    }
} 

sample.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>



<BorderPane prefHeight="610.0" prefWidth="820.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <bottom>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Button fx:id="startButton" mnemonicParsing="false" prefHeight="75.0" prefWidth="200.0" text="START">
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button fx:id="stopButton" mnemonicParsing="false" prefHeight="75.0" prefWidth="200.0" text="STOP">
               <HBox.margin>
                  <Insets left="10.0" />
               </HBox.margin>
            </Button>
         </children>
      </HBox>
   </bottom>
   <center>
      <Label fx:id="textLabel" alignment="CENTER" prefHeight="205.0" prefWidth="696.0" text="Label" BorderPane.alignment="CENTER">
         <font>
            <Font name="Ubuntu Mono" size="72.0" />
         </font>
      </Label>
   </center>
</BorderPane>
1

Słabo szukałeś :)
https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm

Wiem już, że zmiany GUI nie mogą być dokonywane w innych wątkach.

Nie ma w Javie FX czegoś takiego jak wątek GUI. Jest główny wątek obiektu dziedziczącego po klasie Application, w którym między innymi dochodzi do aktualizowania zawartości GUI.

EDIT

Tutaj jeszcze przykład, który powinien Ci pomóc:

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class JavaFXTest extends Application {

    private Task task;
    private Button button;
    private Label label;

    public Parent createContent() {

        /* layout */
        BorderPane layout = new BorderPane();

        /* layout -> center */
        HBox hBox = new HBox(10);

        task = new Task<Void>() {
            @Override
            public Void call() {
                /*
                Po egzekucji "label.textProperty().bind(task.messageProperty());" tekst kontrolki "label" zostaje
                usunięty i należy raz jeszcze zdefiniować dla niej tekst początkowy.
                Stąd "updateMessage("Iterate Me 0");"
                 */
                updateMessage("Iterate Me 0");
                final int max = 100;
                for (int i = 1; i <= max; i++) {
                    try {
                        Thread.sleep(500);
                        String[] str = label.getText().split("\\s");
                        updateMessage(str[0] + " " + str[1] + " " + (Integer.parseInt(str[2]) + 1));
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
                return null;
            }
        };

        button = new Button("Bite Me :*");
        button.setOnAction(ae -> {

            label.textProperty().bind(task.messageProperty());
            new Thread(task).start();
        });

        label = new Label("Iterate Me 0");

        /* layout -> center (add items) */
        hBox.getChildren().addAll(button, label);
        hBox.setAlignment(Pos.CENTER);

        /* return layout */
        layout.setCenter(hBox);
        return layout;
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.setWidth(200);
        stage.setHeight(200);
        
        /*
        Należy uwzględnić zakończenie pracy aplikacji przy kliknięciu na "krzyżyk" okienka. W innym wypadku
        działanie wątku zainicjalizowanego w obiekcie "task" będzie trwać nadal po zamknięciu okna.
         */
        stage.setOnCloseRequest(eh -> {
            System.exit(-1);
        });
        stage.show();
    }

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

Dzięki wielkie! :)
Przeglądałem to wcześniej, ale jakoś odniosłem wrażenie, że nie ma tam akurat tego co potrzebuję.
Teraz się przyjrzałem i faktycznie słabo szukałem.

Naprawiona metoda changeLabel():

    public void changeLabel(){
        Task task = new Task<Void>() {
            @Override public Void call() throws InterruptedException {
                final int max = 100;
                for (int i=1; i<=max; i++) {
                    if (isCancelled) {
                        break;
                    }
                    updateMessage(Integer.toString(i));
                    System.out.println("index = " + i);
                    Thread.sleep(1000);
                }
                return null;
            }
        };
        textLabel.textProperty().bind(task.messageProperty());
        new Thread(task).start();
    }

W reszcie wyciszyłem po chamsku parę wyjątków, ale już trudno. Jeszcze raz serdeczne dzięki.
Pozdrawiam

1 użytkowników online, w tym zalogowanych: 0, gości: 1