Java Timer - Updating Labels with Platform.runLater

岁酱吖の 提交于 2020-08-20 11:16:27

问题


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){

    }

回答1:


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.

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!)
        });
    }
};

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.

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;
    }
  }
}

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

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.



来源:https://stackoverflow.com/questions/61330924/java-timer-updating-labels-with-platform-runlater

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!