Java Timer-使用Platform.runLater更新标签 [英] Java Timer - Updating Labels with Platform.runLater

查看:76
本文介绍了Java Timer-使用Platform.runLater更新标签的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此代码示例是Stopwatch类的一部分,Stopwatch类是一个较大的项目的一部分,该项目是一个以Android Clock为后缀的桌面gui应用程序.我有用于秒,分钟,小时等的标签,这些标签应该从计时器任务内部的无限while循环中更新,而该计时器任务是在布尔状态为true时运行的. while循环应该实时更新GUI标签.我让计时器任务每毫秒执行一次.为什么在程序更新第一个Label时我的GUI会挂起,我该如何解决?下面是代码.

This code sample is part of a Stopwatch class that is part of a larger project that is meant to be a desktop gui app that models after Android's Clock. I have labels for seconds, minutes, hours, etc. that are supposed to be updated from an infinite while loop that is inside a timer task which is ran while a boolean state is true. The while loop is supposed to update the GUI labels with the real time. I have the timer task execute every millisecond. Why does my GUI hang as soon as the program gets to updating the first Label and how can I resolve it? Below is the code.

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包含无限循环.这意味着您在 JavaFX Application Thread 上执行无限循环,这就是UI变得无响应的原因.如果FX线程不能自由执行其工作,则无法处理用户生成的事件,并且无法安排渲染脉冲".后一点是为什么即使您连续调用setText(...),UI也不会更新.

The Runnable you pass to Platform#runLater(Runnable) contains an infinite loop. That means you execute an infinite loop on the JavaFX Application Thread which is why your UI becomes unresponsive. If the FX thread is not free to do its job then no user-generated events can be processed and render "pulses" cannot be scheduled. That latter point is why the UI does not update despite you calling setText(...) continuously.

如果要继续使用当前方法,解决方法是从Runnable实现中删除for (;;)循环.您将TimerTask设置为每毫秒执行一次,这意味着您要做的就是计算新状态并为每次执行设置标签一次.换句话说,run()方法已经被循环"了.例如:

The fix, if you want to continue your current approach, is to remove the for (;;) loop from your Runnable implementation. You setup the TimerTask to be executed once every millisecond which means all you have to do is calculate the new state and set the labels once per execution. In other words, the run() method is already "looped". For example:

TimerTask task = new TimerTask() {
    @Override public void run() {
        Platform.runLater(() -> {
            // calculate new state...

            // update labels...

            // return (no loop!)
        });
    }
};

也就是说,没有理由为此使用后台线程.我建议使用由提供的动画API 取而代之的是JavaFX.它是异步的,但在FX线程上执行,因此更易于实现和推理-使用多个线程总是更加复杂.要执行与当前操作相似的操作,可以使用TimelinePauseTransition代替java.util.Timer. JavaFX定期后台任务问题与解答(Q& A)提供了一些为此目的使用动画的良好示例.

That said, there's no reason to use a background thread for this. I recommend using the animation API provided by JavaFX instead. It's asynchronous but executed on the FX thread, making it simpler to implement and reason about—using multiple threads is always more complicated. To do something similar to what you're currently doing you can use a Timeline or PauseTransition in place of the java.util.Timer. The JavaFX periodic background task Q&A gives some good examples of using animations for this purpose.

我个人会使用 AnimationTimer 实施秒表.这是一个示例:

Personally, I would use an AnimationTimer to implement a stopwatch. Here's an example:

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实例无法被垃圾回收.

Warning: While the AnimationTimer is running the Stopwatch instance cannot be garbage collected.

上面公开了一个属性elapsedTime,它表示经过的时间(以毫秒为单位).根据该值,您可以计算自秒表启动以来经过的天,小时,分钟,秒和毫秒的数量.您只需收听属性并在属性更改时更新UI.

The above exposes a property, elapsedTime, which represents the elapsed time in milliseconds. From that value you can calculate the amount of days, hours, minutes, seconds, and milliseconds that have passed since you started the stopwatch. You simply have to listen to the property and update the UI when the property changes.

这篇关于Java Timer-使用Platform.runLater更新标签的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆