此代码示例属于Stopwatch类的一部分,该类属于一个较大的项目的一部分,该更大的项目的目的是成为模仿Android Clock的桌面gui应用。我具有秒,分钟,小时等标签,这些标签应该从计时器任务内部的无限while循环中更新,而该计时器任务在布尔状态为true时运行。while循环应该实时更新GUI标签。我让计时器任务每毫秒执行一次。为什么我的GUI在程序更新第一个Label后立即挂起,我该如何解决?下面是代码。
static int Milliseconds = 0; static int Seconds = 0; static int Minutes = 0; static int Hours = 0; static int Days = 0; static Boolean State = false; public static void display(){ Stage window = new Stage(); window.initModality(Modality.APPLICATION_MODAL); window.setTitle("Timer"); window.setMinWidth(250); window.setMinHeight(500); GridPane gp = new GridPane(); Label days = new Label("0"); gp.setConstraints(days, 0,0); Label hours = new Label("0"); gp.setConstraints(hours, 1,0); Label minutes = new Label("0"); gp.setConstraints(minutes,2,0); Label seconds = new Label("0"); gp.setConstraints(seconds,3,0); Label milliseconds = new Label("0"); gp.setConstraints(milliseconds, 4,0); //Handler mainHandler = new Handler() // Task<Void> longRunningTask = new Task<Void>(){} Timer mt = new Timer(); //Platform.runLater is not updating gui. It hangs the gui instead TimerTask tm = new TimerTask() { @Override public void run() { Platform.runLater(() -> { for (; ; ) { long timebefore = System.currentTimeMillis(); if (State) { try { if (Milliseconds > 999) { Milliseconds = 0; Seconds++; } if (Seconds > 59) { Milliseconds = 0; Seconds = 0; Minutes++; } if (Minutes > 59) { Milliseconds = 0; Seconds = 0; Minutes = 0; Hours++; } if (Hours > 23) { Milliseconds = 0; Seconds = 0; Minutes = 0; Hours = 0; Days++; } milliseconds.setText(" : " + Milliseconds); Milliseconds++; seconds.setText(" : " + Seconds); minutes.setText(" : " + Minutes); hours.setText(" : " + Hours); days.setText(" : " + Days); } catch (Exception e) { e.printStackTrace(); } } } }); } }; Button start = new Button("Start"); gp.setConstraints(start, 0,1); start.setOnAction(event -> { State = true; mt.scheduleAtFixedRate(tm, 1,1); }); Button stop = new Button("Stop"); gp.setConstraints(stop,1,1); stop.setOnAction(event-> { State = false; }); Button restart = new Button("Restart"); gp.setConstraints(restart, 2,1); restart.setOnAction(event-> { State = false; Milliseconds = 0; Seconds = 0; Minutes = 0; Hours = 0; Days = 0; }); gp.getChildren().addAll(milliseconds,seconds, minutes, hours, days, start, stop, restart); Scene scene = new Scene(gp); window.setScene(scene); window.showAndWait(); } public void Start(Timer mt){ }
将Runnable传递给Platform#runLater(Runnable)包含一个无限循环。这意味着您在 JavaFX Application Thread 上执行了无限循环,这就是UI变得无响应的原因。如果FX线程不能自由执行其工作,则无法处理用户生成的事件,并且无法安排渲染“脉冲”。后一点就是为什么即使您setText(...)不断调用UI也不更新。
Runnable
Platform#runLater(Runnable)
setText(...)
如果要继续使用当前方法,解决方法是for (;;)从Runnable实现中删除循环。您将设置为TimerTask每毫秒执行一次,这意味着您要做的就是计算新状态并为每次执行设置一次标签。换句话说,该run()方法已经“循环”。例如:
for (;;)
TimerTask
run()
TimerTask task = new TimerTask() { @Override public void run() { Platform.runLater(() -> { // calculate new state... // update labels... // return (no loop!) }); } };
也就是说,没有理由为此使用后台线程。我建议改用JavaFX提供的动画API。它是异步的,但在FX线程上执行,因此更易于实现和推理- 使用多个线程总是更复杂。要执行与当前操作类似的操作,可以使用Timeline或PauseTransition代替java.util.Timer。在JavaFX的周期性后台任务 Q&A给出了使用动画用于此目的的一些很好的例子。
Timeline
PauseTransition
java.util.Timer
就个人而言,我将使用AnimationTimer来实现秒表。这是一个例子:
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实例时,无法进行垃圾回收。
Stopwatch
上面提供了一个属性,elapsedTime表示经过的时间(以毫秒为单位)。根据该值,您可以计算自秒表启动以来经过的天,小时,分钟,秒和毫秒的数量。您只需收听属性并在属性更改时更新UI。
elapsedTime