Reage
Reage

Reputation: 21

JavaFX, countdown timer in Label setText

I'm building app for making tests. I have a scene made in SceneBuilder. There is a ImageView under image Label with Question and 3 buttons "A", "B", "C", Texts to buttons and question's Label are taken from DataBase, if you click answer new Image, question and asnwers are loading, everything works, but i want to add a Timer in the corner. When image and question show on the screen there will be "Time to read the question" and countdown from 10 to 0, and then "Time to answer" and again countdown from 10 to 0. If timer ends and there is no answer question and image will change automaticly. But the problem is that, i can do the timer, its counting down, and after this time it change the question but I dont know how to put it into Label. If inside Timer I do something like seconds-- label.setText(seconds) there is no error but when I start app there is a lot of exceptions. Can you help me how I can put this variable which is decrementing in timer after each second to Label ?

public void setTimer() {
    timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            if(interval > 0)
            {
                timeElapsed.setText("Time to read the question: "+interval);
                System.out.println(interval);
                interval--; 
            }
            else
                timer.cancel();
        }
    }, 1000,1000);
}

Now i have something like this, in console everything is working there is a countdown from 10 to 0 but no effect in scene.

And errors:

Exception in thread "Timer-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = Timer-0
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:493)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:271)
at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.lambda$new$11(LabeledSkinBase.java:219)
at javafx.controls/com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler.lambda$new$1(LambdaMultiplePropertyChangeListenerHandler.java:49)
at javafx.base/javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
at javafx.base/javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:104)
at javafx.base/javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:111)
at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:145)
at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:50)
at javafx.base/javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.controls/javafx.scene.control.Labeled.setText(Labeled.java:147)
at gui.controller.StudentTestYesNoController$1.run(StudentTestYesNoController.java:40)
at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
at java.base/java.util.TimerThread.run(Timer.java:506)

Upvotes: 0

Views: 12946

Answers (2)

DiamondMiner88
DiamondMiner88

Reputation: 29

Adding on to what mr mcwolf said above, i think you need to set

Platform.setImplicitExit(false);

before Platform.runLater(); so that it will run every time the task is called.

Upvotes: 0

mr mcwolf
mr mcwolf

Reputation: 2859

The problem is that you are trying to change the UI from a thread other than the application.

This should solve the problems with your current implementation

public void setTimer() {
    timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            if(interval > 0)
            {
                Platform.runLater(() -> timeElapsed.setText("Time to read the question: "+interval));
                System.out.println(interval);
                interval--;
            }
            else
                timer.cancel();
        }
    }, 1000,1000);
}

Also, you can take a look at something specific about the javafx - Timeline

JavaFX periodic background task

Upvotes: 3

Related Questions