Как в javaFX обновить Label из второго потока

159
15 сентября 2019, 12:30

Пытаюсь написать таймер обратного отсчета времени. В Label lbTimerCountDown должен отображаться текущее состояние времени в виде минуты:секунды "mm:ss" . Таймер запускаю во втором потоке и зависает основной поток пока не остановится второй. Вот реализация:

Main

public class Main extends Application {
     public static Controller controller;
    @Override
    public void start(Stage primaryStage) throws Exception{

    FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
    Parent root = loader.load();
    controller = loader.getController();
    primaryStage.setTitle("Таймер");
    primaryStage.setScene(new Scene(root));
    primaryStage.show();
}

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

Основной поток

import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class Controller {
@FXML
private TextField tfMinute;
@FXML
private Label lbTimerCountDown;
@FXML
private Button btnStartCountDown;
public Label getLbTimerCountDown() {
    return lbTimerCountDown;
}
public void initialize(){
    btnStartCountDown.addEventHandler(ActionEvent.ANY, actionEventHandler);
}

private EventHandler<Event> actionEventHandler = new EventHandler<Event>() {
    @Override
    public void handle(Event event) {
        if (event.getSource() == btnStartCountDown) {
            CountDown countDown = new CountDown();
            countDown.start();
        }
    }};
}

второй поток

import javafx.application.Platform;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class CountDown extends Thread {

private LocalTime timerCountDown;
private DateTimeFormatter dtfLocalTime;
private DateTimeFormatter dtfLocalTimeMinuteSecund;

@Override
public void run() {
    Platform.runLater(new Runnable() {
        @Override
        public void run() {
                dtfLocalTime = DateTimeFormatter.ofPattern("HH:mm:ss");
                dtfLocalTimeMinuteSecund = DateTimeFormatter.ofPattern("mm:ss");
                while (!Main.controller.getLbTimerCountDown().getText().equals("00:00")) {
                    timerCountDown = LocalTime.parse("00:" + Main.controller.getLbTimerCountDown().getText(), dtfLocalTime);
                    timerCountDown = timerCountDown.minusSeconds(1);
                    Main.controller.getLbTimerCountDown().setText(timerCountDown.format(dtfLocalTimeMinuteSecund));
                    System.out.println(timerCountDown.format(dtfLocalTimeMinuteSecund));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        }
    }); 
}
}

насчет Platform.runLater(new Runnable()) я не уверен нужен ли? с ним или без него основной поток висит одинаково.

Answer 1

JavaFX, как и многие другие GUI-библиотеки, однопоточна. Вы не должны пытаться из главного потока или любого другого потока взаимодействовать с элементами графического интерфейса - это приведёт к сбою. Вы не должны внутри обработчиков событий запускать потоки - это приведёт к сбою. И вы не должны останавливать поток - это приведёт к сбою.

Platform.runLater() не предназначен для продолжительных задач, его стоит использовать только для разового изменения UI. Например, он подойдёт для вызова из потока, который получает текст по ссылке, устанавливает этот текст в поле и завершает работу. Для задач, который требуют периодического взаимодействия потока с UI нужно использовать Task или Timeline.

Answer 2

Проблема была в неправильно реализованной команде Platform.runLater(() -> немного поправив код заработал как задумывалось... возможно я не правильно решил проблему, буду рад если подскажите реализацию задачи иначе.

@Override
public void run() {
    dtfLocalTime = DateTimeFormatter.ofPattern("HH:mm:ss");
    dtfLocalTimeMinuteSecund = DateTimeFormatter.ofPattern("mm:ss");
    while (!Main.controller.getLbTimerCountDown().getText().equals("00:00")) {
        try {
            timerCountDown = LocalTime.parse("00:" + Main.controller.getLbTimerCountDown().getText(), dtfLocalTime);
            timerCountDown = timerCountDown.minusSeconds(1);
            Platform.runLater(() -> Main.controller.getLbTimerCountDown().setText(timerCountDown.format(dtfLocalTimeMinuteSecund)));
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("timer end");
}
READ ALSO
Maven. Зависимость дочерних модулей модулей

Maven. Зависимость дочерних модулей модулей

Как приавильно указать зависимость между дочерними модулями? У меня есть модуль server и clientВторой зависит от первого

168
Проблема с подключением JavaFX в intellij IDEA

Проблема с подключением JavaFX в intellij IDEA

Добрый день, Я новичок в программирование, дошёл до изучения JavaFXНо возникли некоторые проблемы

174