Runnable
, который вы передаете Platform#runLater(Runnable)
, содержит бесконечное число l oop. Это означает, что вы выполняете бесконечное число l oop в потоке приложений JavaFX , поэтому ваш пользовательский интерфейс перестает отвечать на запросы. Если поток FX не свободен выполнять свою работу, то никакие сгенерированные пользователем события не могут быть обработаны, и рендеринг "импульсов" не может быть запланирован. Последний момент заключается в том, что пользовательский интерфейс не обновляется, несмотря на то, что вы постоянно звоните setText(...)
.
Исправление, если вы хотите продолжить текущий подход, заключается в удалении for (;;)
l oop из вашего Runnable
реализация. Вы устанавливаете TimerTask
, чтобы он выполнялся один раз в каждую миллисекунду, что означает, что все, что вам нужно сделать, - это вычислить новое состояние и установить метки один раз за выполнение. Другими словами, метод run()
уже «зациклен». Например:
TimerTask task = new TimerTask() {
@Override public void run() {
Platform.runLater(() -> {
// calculate new state...
// update labels...
// return (no loop!)
});
}
};
Тем не менее, нет причин использовать фоновый поток для этого. Вместо этого я рекомендую использовать API анимации , предоставляемый JavaFX. Он асинхронный, но выполняется в потоке FX, что упрощает его реализацию и анализ - использование нескольких потоков всегда сложнее. Чтобы сделать что-то похожее на то, что вы делаете в данный момент, вы можете использовать Timeline
или PauseTransition
вместо java.util.Timer
. JavaFX periodi c фоновая задача Q & A дает несколько хороших примеров использования анимации для этой цели.
Лично я бы использовал AnimationTimer
для реализации секундомер. Вот пример:
import java.util.concurrent.TimeUnit;
import javafx.animation.AnimationTimer;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
public class Stopwatch {
private static long toMillis(long nanos) {
return TimeUnit.NANOSECONDS.toMillis(nanos);
}
// value is in milliseconds
private final ReadOnlyLongWrapper elapsedTime = new ReadOnlyLongWrapper(this, "elapsedTime");
private void setElapsedTime(long elapsedTime) { this.elapsedTime.set(elapsedTime); }
public final long getElapsedTime() { return elapsedTime.get(); }
public final ReadOnlyLongProperty elapsedTimeProperty() { return elapsedTime.getReadOnlyProperty(); }
private final ReadOnlyBooleanWrapper running = new ReadOnlyBooleanWrapper(this, "running");
private void setRunning(boolean running) { this.running.set(running); }
public final boolean isRunning() { return running.get(); }
public final ReadOnlyBooleanProperty runningProperty() { return running.getReadOnlyProperty(); }
private final Timer timer = new Timer();
public void start() {
if (!isRunning()) {
timer.start();
setRunning(true);
}
}
public void stop() {
if (isRunning()) {
timer.pause();
setRunning(false);
}
}
public void reset() {
timer.stopAndReset();
setElapsedTime(0);
setRunning(false);
}
private class Timer extends AnimationTimer {
private long originTime = Long.MIN_VALUE;
private long pauseTime = Long.MIN_VALUE;
private boolean pausing;
@Override
public void handle(long now) {
if (pausing) {
pauseTime = toMillis(now);
pausing = false;
stop();
} else {
if (originTime == Long.MIN_VALUE) {
originTime = toMillis(now);
} else if (pauseTime != Long.MIN_VALUE) {
originTime += toMillis(now) - pauseTime;
pauseTime = Long.MIN_VALUE;
}
setElapsedTime(toMillis(now) - originTime);
}
}
@Override
public void start() {
pausing = false;
super.start();
}
void pause() {
if (originTime != Long.MIN_VALUE) {
pausing = true;
} else {
stop();
}
}
void stopAndReset() {
stop();
originTime = Long.MIN_VALUE;
pauseTime = Long.MIN_VALUE;
pausing = false;
}
}
}
Предупреждение: Во время работы AnimationTimer
экземпляр Stopwatch
не может быть собран сборщиком мусора.
Выше представлено свойство, elapsedTime
, которое представляет истекшее время в миллисекундах. Из этого значения вы можете рассчитать количество дней, часов, минут, секунд и миллисекунд, прошедших с момента запуска секундомера. Вам просто нужно прослушать свойство и обновить пользовательский интерфейс при изменении свойства.