java - Java 计时器 - 使用 Platform.runLater 更新标签
问题描述
此代码示例是 Stopwatch 类的一部分,该类是更大项目的一部分,该项目旨在成为仿照 Android 时钟建模的桌面 gui 应用程序。我有秒、分钟、小时等的标签,这些标签应该从一个无限循环中更新,该循环位于一个计时器任务内,该任务在布尔状态为真时运行。while 循环应该实时更新 GUI 标签。我让计时器任务每毫秒执行一次。为什么程序一开始更新第一个标签,我的 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){
}
解决方案
Runnable
你传递给包含Platform#runLater(Runnable)
一个无限循环。这意味着您在JavaFX 应用程序线程上执行无限循环,这就是您的 UI 变得无响应的原因。如果 FX 线程不能自由地完成其工作,则无法处理任何用户生成的事件,并且无法安排渲染“脉冲”。setText(...)
后一点就是为什么即使您连续调用 UI 也不会更新的原因。
如果您想继续当前的方法,解决方法是从您的实现中删除for (;;)
循环。Runnable
您设置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 定期后台任务问答提供了一些为此目的使用动画的好例子。
就个人而言,我会使用 anAnimationTimer
来实现秒表。这是一个例子:
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。
推荐阅读
- gitlab - 在 gitlab CI 中链接服务
- data-analysis - 如何在 Zoho Analytics 中使用 0 应用过滤器后显示所有数据
- node.js - 纯 Node.js 中的 Android 暗模式检测
- javascript - 如何使用 puppeteer 从输入只读类中读取值?
- android - 为什么 PowerMockito 会抛出 TooManyActualInvocations?
- django - Django下拉列表不保存价值
- java - 如何在 Spring Boot 服务中使用多态性?
- javascript - 使用 useEffect 时超出最大深度
- ios - 在 iOS 上设置 Firebase FCM 时出错:Flutter
- git - git-receive-pack 使用双引号而不是单引号,而 git 使用单引号调用它