Таймер в JavaFX, обновляющий метку (привязки)

Я хочу добавить в свое приложение часы, которые сообщают, как долго вы выполняете задачу. Чтобы упростить его, я включил счетчик, который увеличивается каждую секунду в новом потоке, и обновляю метку setTimer номером счетчика. Для этого у меня есть метка fx:id="setTimer" в моем файле .fxml, и я импортировал ее в свой класс.

 @FXML
    private Label setTimer;

И создал еще один класс в моем классе, который расширяет поток TimerTask и увеличивает счетчик на единицу при каждом вызове. Создан новый объект «текст», который всегда должен обновляться с текущим значением счетчика.

    SimpleStringProperty text = new SimpleStringProperty("undefined");
public class MyTask extends TimerTask {
        @Override
        public void run() {
            counter++;
            text.set(Integer.toString(counter));
        }
    }

Чтобы этот класс вызывался каждую секунду, я создал таймер в методе инициализации и установил его на одну секунду.

@Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        MyTask myTask = new MyTask();
        Timer timer = new Timer(true);
        timer.scheduleAtFixedRate(myTask, 0 , 1000);
        setTimer.textProperty().bind(text);
    }

На данный момент я получаю исключение «Не в потоке приложения FX; currentThread = Таймер-0'.

Я пробовал много способов решить свою проблему, но я не дошел до нужного момента. Мое представление о том, что я хочу сделать, должно быть ясным, и я был бы рад, если бы кто-то мог мне помочь. Моя проблема заключается в обновлении изменений счетчика в графическом интерфейсе. Это не нужно решать так, как я думал, просто нужен совет, как лучше всего это реализовать.

Спасибо

Добро пожаловать в СО. Возможно, вы сможете использовать секундомер, например org.apache.commons.lang.time.StopWatch (документация: commons.apache.org/proper/commons-lang/apidocs/org/apache/…)‌​. Запустите его при запуске вашего приложения и используйте getTime, чтобы получить время. Я не уверен, есть ли порперти для привязки или нет.

Jakob Tinhofer 21.12.2020 12:04

Кроме того, чтобы избежать исключения при выполнении чего-либо из другого потока, можно избежать, если вы поместите изменяющийся код внутрь Platform.runLater(new Runnable(){ /* ... */ });

Jakob Tinhofer 21.12.2020 12:05

a) использовать поддержку параллелизма/анимации fx b) что бы вы ни использовали, вы не должны обновлять узлы (или какие-либо их свойства) вне потока приложения fx c) при запросе помощи в отладке предоставьте минимально воспроизводимый пример демонстрирующий, что не работает, как ожидалось

kleopatra 21.12.2020 12:35
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
684
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Хорошо, мои комментарии слишком длинные. Вот как бы я попробовал это сделать.

  1. Запустите секундомер в загружаемом приложении
  2. Создайте новый поток, который время от времени запускает сам себя.
  3. Внутри получите время от секундомера в секундах (sw.getTime(TimeUntis.seconds)). Преобразуйте это в часы и минуты, если хотите , как показано в этом посте SO
  4. Затем напишите время в пользовательском интерфейсе, используя Platform.runLater(new Runnable(){ /* access ui element and write time here */ });

Олег, я так долго решал эту проблему. Вы очень помогли мне с «Platform.runLater (new Runnable())», и теперь он работает. Я добавил это в свой run() в классе MyTask Platform.runLater (new Runnable () { @Override public void run () { text.set (Integer.toString (counter)); }}); Большое спасибо.

Jens 21.12.2020 14:28

Не за что, всегда рад помочь!

Jakob Tinhofer 21.12.2020 20:38

Использование Platform.runLater() в фоновом потоке — это своего рода грязный хлам, которого, вероятно, следует избегать. JavaFX имеет механизмы для обработки такого рода вещей, которые вы должны использовать. В частности, Task<> позволяет фоновым потокам обновлять данные, связанные с элементами экрана JavaFX, которые необходимо обновить на FXAT.

Вы МОЖЕТЕ делать то, что пытаетесь сделать с помощью задачи JavaFX, но использование таймера Java внутри нее кажется невозможным, поскольку поток Java не может ожидать завершения таймера. Итак, вместо этого я использовал цикл for со сном, чтобы сделать то же самое. Это неуклюже, но демонстрирует, как связать частичные результаты задачи с отображением на экране:

public class Sample1 extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        Scene scene = new Scene(new Timer1(), 300, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

public class Timer1 extends VBox {

    public Timer1() {
        Text time = new Text();
        Button startButton = new Button("Start");
        Button stopButton = new Button("Stop");
        getChildren().addAll(time, startButton, stopButton);
        startButton.setOnAction(startEvt -> {
            Task<Integer> timerFxTask = new Task<>() {

                {
                    updateValue(0);
                }

                @Override
                protected Integer call() throws Exception {
                    for (int counter = 0; counter <= 1000; counter++) {
                        sleep(1000);
                        updateValue(counter);
                    }
                    return 1000;
                }
            };
            stopButton.setOnAction(stopEvt -> timerFxTask.cancel());
            time.textProperty().bind(Bindings.createStringBinding(() -> timerFxTask.getValue().toString(),
                    timerFxTask.valueProperty()));
            Thread timerThread = new Thread(timerFxTask);
            timerThread.start();
        });
    }
}

Но есть лучший способ сделать то, что вы пытаетесь сделать, который по сути является анимацией, и у JavaFX есть возможность сделать именно это. Обычно люди используют анимацию для преобразования внешнего вида элементов экрана JavaFX, но вы также можете использовать ее для анимации содержимого текста с течением времени. Что я сделал здесь, так это создал IntegerProperty, которое может быть преобразовано из начального значения в конечное значение, линейно интерполированное во времени, а затем привязал это значение к TextProperty текста на экране. Таким образом, вы видите, что он обновляется один раз в секунду.

public class Sample1 extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        Scene scene = new Scene(new Timer2(), 300, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

public class Timer2 extends VBox {

    public Timer2() {
        Text time = new Text();
        Button startButton = new Button("Start");
        Button stopButton = new Button("Stop");
        getChildren().addAll(time, startButton, stopButton);
        startButton.setOnAction(startEvt -> {
            IntegerProperty counter = new SimpleIntegerProperty(0);
            Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1000), new KeyValue(counter, 1000)));
            stopButton.setOnAction(stopEvt -> timeline.stop());
            time.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(counter.get()), counter));
            timeline.play();
        });
    }
}

Спасибо за уделенное время, завтра обязательно попробую

Jens 21.12.2020 19:25

Другие вопросы по теме