Java Таймер - обновление меток с помощью Platform.runLater - PullRequest
0 голосов
/ 20 апреля 2020

Этот пример кода является частью класса Stopwatch, который является частью более крупного проекта, предназначенного для настольного приложения gui, которое моделирует часы Android. У меня есть ярлыки на секунды, минуты, часы и т. Д. c. которые должны быть обновлены с бесконечного времени l oop, находящегося внутри задачи таймера, которая выполняется, пока булево состояние истинно. В то время как l oop должен обновлять метки 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){

    }

1 Ответ

1 голос
/ 21 апреля 2020

Runnable, который вы передаете Platform#runLater(Runnable), содержит бесконечное число l oop. Это означает, что вы выполняете бесконечное число l oop в потоке приложений JavaFX , поэтому ваш пользовательский интерфейс перестает отвечать на запросы. Если поток FX не свободен выполнять свою работу, то никакие сгенерированные пользователем события не могут быть обработаны, и рендеринг "импульсов" не может быть запланирован. Последний момент заключается в том, что пользовательский интерфейс не обновляется, несмотря на то, что вы постоянно звоните setText(...).

Исправление, если вы хотите продолжить текущий подход, заключается в удалении for (;;) l oop из вашего Runnable реализация. Вы устанавливаете TimerTask, чтобы он выполнялся один раз в каждую миллисекунду, что означает, что все, что вам нужно сделать, - это вычислить новое состояние и установить метки один раз за выполнение. Другими словами, метод run() уже «зациклен». Например:

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

            // update labels...

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

Тем не менее, нет причин использовать фоновый поток для этого. Вместо этого я рекомендую использовать API анимации , предоставляемый JavaFX. Он асинхронный, но выполняется в потоке FX, что упрощает его реализацию и анализ - использование нескольких потоков всегда сложнее. Чтобы сделать что-то похожее на то, что вы делаете в данный момент, вы можете использовать Timeline или PauseTransition вместо java.util.Timer. JavaFX periodi c фоновая задача Q & A дает несколько хороших примеров использования анимации для этой цели.

Лично я бы использовал 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, которое представляет истекшее время в миллисекундах. Из этого значения вы можете рассчитать количество дней, часов, минут, секунд и миллисекунд, прошедших с момента запуска секундомера. Вам просто нужно прослушать свойство и обновить пользовательский интерфейс при изменении свойства.

...