[英]Java Timer - Updating Labels with Platform.runLater
此代码示例是 Stopwatch class 的一部分,它是一个更大的项目的一部分,该项目旨在成为仿照 Android 时钟的桌面 gui 应用程序。 我有秒、分钟、小时等的标签,这些标签应该从无限循环中更新,该循环位于计时器任务内,当 boolean state 为真时运行。 while 循环应该实时更新 GUI 标签。 我让计时器任务每毫秒执行一次。 为什么程序一开始更新第一个 Label,我的 GUI 就会挂起,我该如何解决? 下面是代码。
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){
}
您传递给Platform#runLater(Runnable)
Runnable
Runnable 包含一个无限循环。 这意味着您在JavaFX 应用程序线程上执行无限循环,这就是您的 UI 变得无响应的原因。 如果 FX 线程不能自由地完成其工作,则无法处理任何用户生成的事件,并且无法安排渲染“脉冲”。 后一点是尽管您连续调用setText(...)
,但 UI 不会更新的原因。
如果您想继续当前的方法,解决方法是从Runnable
实现中删除for (;;)
循环。 您将TimerTask
设置为每毫秒执行一次,这意味着您所要做的就是计算新的 state 并在每次执行时设置一次标签。 换句话说, run()
方法已经“循环”了。 例如:
TimerTask task = new TimerTask() {
@Override public void run() {
Platform.runLater(() -> {
// calculate new state...
// update labels...
// return (no loop!)
});
}
};
也就是说,没有理由为此使用后台线程。 我推荐使用JavaFX提供的 animation API 代替。 它是异步的,但在 FX 线程上执行,使其更易于实现和推理——使用多个线程总是更复杂。 要执行与您当前正在执行的操作类似的操作,您可以使用Timeline
或PauseTransition
代替java.util.Timer
。 JavaFX 定期后台任务问答提供了一些为此目的使用动画的好例子。
就个人而言,我会使用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
,它以毫秒为单位表示经过的时间。 根据该值,您可以计算自启动秒表以来经过的天数、小时数、分钟数、秒数和毫秒数。 您只需要监听属性并在属性更改时更新 UI。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.