首页 > 解决方案 > 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){

    }

标签: javauser-interfacejavafxlabelstopwatch

解决方案


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 线程上执行,使其更易于实现和推理——使用多个线程总是更复杂。要执行与您当前正在执行的操作类似的操作,您可以使用TimelinePauseTransition代替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。


推荐阅读