# Countdown in Label anzeigen



## fabipfolix (4. Sep 2018)

Hallo,

ich scheitere leider an dem Versuch einen Timer, welcher in der Konsole problemlos funktioniert, in einer GUI anzeigen zu lassen.

Das Problem ist, dass ich innerhalb des TimerTasks nicht auf mein Label, welches die Zeit anzeigen soll, zugreifen kann.



Spoiler: SampleController.java





```
package application;

import java.util.Timer;
import java.util.TimerTask;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

public class SampleController {
   
    static int seconds,interval;
   
    static Timer timer;
   
    @FXML
    private Button btnStart;

    @FXML
    private Label LabelTime;

    @FXML
    void btnStartPressed(ActionEvent event) {
        countdown();
    }

public void countdown() {
       
        int seconds = 60;
       
         int delay = 1000;
            int period = 1000;
            timer = new Timer();
            interval = (seconds);
            System.out.println(seconds);
           
            timer.scheduleAtFixedRate(new TimerTask() {

                public void run() {
                   

                    System.out.println(setInterval());                
//                  LabelTime.setText(""+setInterval());  Das ist das Problem

                }
            }, delay, period);
       
    }
   
    private static final int setInterval() {
        if (interval == 1)
            timer.cancel();
        return --interval;
    }
}
```



Falls die Antwort nicht sowieso klar ist, hier die Error-Message, wenn ich auf das Label zugreifen will:


Spoiler: Error





```
Exception in thread "Timer-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = Timer-0
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:279)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
    at javafx.scene.Parent$2.onProposedChange(Parent.java:367)
    at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
    at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
    at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:575)
    at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:204)
    at com.sun.javafx.scene.control.skin.LabelSkin.handleControlPropertyChanged(LabelSkin.java:49)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(BehaviorSkinBase.java:197)
    at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
    at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
    at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:144)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
    at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
    at javafx.scene.control.Labeled.setText(Labeled.java:145)
    at application.SampleController$1.run(SampleController.java:45)
    at java.util.TimerThread.mainLoop(Unknown Source)
    at java.util.TimerThread.run(Unknown Source)
```



Weiß jemand, wie ich das richtig mache?
 Das selbe Problem habe ich auch, wenn etwas in einem anderen Thread abläuft.


----------



## Flown (5. Sep 2018)

Kurze Antwort: Du greifst auf ein UI-Element außerhalb des UI-Threads zu. Lösung: die Operation in `Platform.runLater` ausführen.

https://stackoverflow.com/questions...alstateexception-not-on-fx-application-thread

Oder etwas länger:
https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm


----------



## fabipfolix (5. Sep 2018)

Danke, ich werds mir morgen mal anschauen


----------



## fabipfolix (6. Sep 2018)

Flown hat gesagt.:


> Kurze Antwort: Du greifst auf ein UI-Element außerhalb des UI-Threads zu. Lösung: die Operation in `Platform.runLater` ausführen.
> 
> https://stackoverflow.com/questions...alstateexception-not-on-fx-application-thread



Wie ich Platform.runLater() bei mir benutzte bekomm ich leider nicht do ganz hin.

Ich würde das ganze jetzt versuchen mit einem ScheduledService, welcher Thread.sleep(1) beinhaltet und dann 45 mal wiederholt wird. Und bei jedem onSucceded Event das Label um 1 verringern. 

Wäre die andere Möglichkeit leichter/Funktioniert das oben genannte überhaupt? (kann es leider erst später testen)?


----------



## looparda (6. Sep 2018)

Platform.runLater so:

```
Platform.runLater(() -> LabelTime.setText(""+setInterval()));
```

Und ein ScheduledService lässt sich schedulen.. ScheduledService#setPeriod, ScheduledService#setDelay und hat eine prop ReadOnlyObjectProperty<V> lastValue, die du an die Textproperty eines Textfeldes binden kannst.


----------



## fabipfolix (6. Sep 2018)

looparda hat gesagt.:


> Platform.runLater so:
> 
> ```
> Platform.runLater(() -> LabelTime.setText(""+setInterval()));
> ...



Jetzt wo ich das les macht das mit runLater natürlich Sinn... 
Irgendwie war ich überzeugt davon, dass es man damit einen Task o. ä ausführt.

Danke für eure Hilfe


----------



## looparda (6. Sep 2018)

Ist ein Runnable, welches dann eben genau vom UI-Thread ausgeführt wird.


----------



## fabipfolix (10. Sep 2018)

Den Countdown hab ich jetzt in ein anderes Programm implementiert. Im Prinzip funktioniert er auch, nur in der neuen Version entsteht beim zweiten Durchlauf des Timers ein Fehler, wenn das Label überschrieben werden soll.
Der Output in der Konsole funktioniert.

Es muss ja daran liegen, dass der Timer in einem ScheduledService aufgerufen wird, denn in der obigen Version hat es ja funktioniert.


```
package sample;

//Imports ausgelassen

public class Connector{
   
    ScheduledService service;
    MyTimer myTimer = new MyTimer();

    public Connector(Controller controller){

            service = new ScheduledService() {
                @Override
                protected Task createTask() {
                    return new Task() {
                        @Override
                        protected Object call() throws Exception {

                            if (getActive()) {
                   
                                 myTimer = new MyTimer();

                                 try {
                                        myTimer.countdown(15000, controller); //ERROR nach zweitem Durchlauf
                                 }catch(Exception e){
                                        System.out.println("Error at Timer");
                                        e.printStackTrace();
                                 }
                               
                                 System.out.println("[INFO] Start Timer");

                                 try {
                                      Thread.sleep(15000);
                                 } catch (InterruptedException e) {
                                       e.printStackTrace();
                                }

                                return null;

                            }


                    };
                }
            };
        }

}
```



Spoiler: MyTimer





```
package sample;

public class MyTimer {
    static int interval;
    static Timer timer=new Timer();
    Controller controller;

    public void countdown(int duration, Controller controller) {
            this.controller = controller;

            int delay = 1000;
            int period = 1000;

            interval = (duration);
            System.out.println("[TIMER] "+duration+" Seconds left!");

            Platform.runLater(() ->controller.getLabelTime().setText(""+duration));

            timer.scheduleAtFixedRate(new TimerTask() {
                public void run() {

                    int timeLeft = setInterval();

                    System.out.println("[TIMER] "+timeLeft +" Seconds left!");
                    Platform.runLater(() -> controller.getLabelTime().setText(""+timeLeft));

                    if(timeLeft<=5){
                       Platform.runLater(() ->controller.getBackground().setStyle("-fx-background-color: #ff0000"));
                    } else if(timeLeft<=10){
                       Platform.runLater(() ->controller.getBackground().setStyle("-fx-background-color: #ff9400"));
                    }

                    if(!controller.isActive()|| timeLeft<=0)
                        stopTimer();

             }
        }, delay, period);
    }

    private static final int setInterval() { 
        return --interval;
    }

      public void stopTimer(){

            timer.cancel();
            timer.purge();
            Platform.runLater(() -> controller.getBackground().setStyle(null));
            Platform.runLater(() -> controller.getLabelTime().setText("..."));


       }
}
```




Tut mir leid, falls die Formatierung nicht ganz passt, aber das wurde beim kopieren gerade wild zerstreut.


----------



## looparda (10. Sep 2018)

Was für ein Fehler?
Der Controller fehlt - benutzt du überhaupt Funktionalität des ScheduledService außer die Taskausführung?
Was hast du überhaupt vor? Wieso startest du mehrere Timer?


----------



## fabipfolix (10. Sep 2018)

looparda hat gesagt.:


> Was für ein Fehler?


Tut mir leid, den hab ich grad ganz vergessen.
Hier:


Spoiler: Fehler



java.lang.IllegalStateException: Timer already cancelled.
    at java.util.Timer.sched(Timer.java:397)
    at java.util.Timer.scheduleAtFixedRate(Timer.java:328)
    at utils.fxutils.MyTimer.countdownForDefTimer(MyTimer.java:81)
    at sample.Connector$1$1.call(Connector.java:57)
    at javafx.concurrent.Task$TaskCallable.call(Task.java:1423)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at javafx.concurrent.Service.lambda$null$493(Service.java:725)
    at java.security.AccessController.doPrivileged(Native Method)
    at javafx.concurrent.Service.lambda$executeTask$494(Service.java:724)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)



Das Problem scheint ja zu sein, dass der Timer gecancelt wurde, aber das wurde er doch oben ebenso, oder?



looparda hat gesagt.:


> Der Controller fehlt


Der Controller tut nicht wirklich was zur Sache, wird ja nur für die Getter verwendet, welche Label etc zurückgeben. Das funktioniert problemlos.



looparda hat gesagt.:


> benutzt du überhaupt Funktionalität des ScheduledService außer die Taskausführung?


Ich hab ein if statement ausgelassen, welches in noch einer neuen Klasse etwas überprüft, gibt aber auch nur true/false zurück, also eigentlich auch irrelevant und der Sinn hinter dem Scheduled Service ist, dass es sich wiederholt. Theoretisch wäre also auch ein Service mit einer while möglich.



looparda hat gesagt.:


> Was hast du überhaupt vor? Wieso startest du mehrere Timer?


Ich will überprüfen an einem Screenshot überprüfen ob ein bestimmter Pixel eine passende Farbe hat. Wenn das zutrifft startet der Countdown.  (das ist das oben genannte If-Statement)
Und mehrere Timer starten, da das mehrmals geschehen soll.


----------



## looparda (10. Sep 2018)

Ich hab versucht es zu reproduzieren und den Kram mit dem Background und isActive rausgeschmissen, da mir der Controller fehlt. Aber ich erhalte keine Exception.

Ich habe die Sekunden von 1500 auf 5 gestellt und kann es nun reproduzieren.

Das Problem ist der

```
public class MyTimer {
    static Timer timer=new Timer();
```
in deinem MyTimer. Von diesem hast du nur eine Instanz zur Laufzeit und du cancelst ihn ein zweites mal.


----------



## fabipfolix (10. Sep 2018)

das heißt, bei dir läuft der Countdown in Dauerschleife? (Auch auf einem Label?)


----------



## fabipfolix (10. Sep 2018)

looparda hat gesagt.:


> ```
> public class MyTimer {
> static Timer timer=new Timer();
> ```
> in deinem MyTimer. Von diesem hast du nur eine Instanz zur Laufzeit und du cancelst ihn ein zweites mal.



Aber wieso tritt der Fehler dann auf sobald ich den Timer ein zweites Mal starte und nicht wenn ich ihn zum zweiten mal cancel.

Diese Zeile verursacht den Fehler: 

```
timer.scheduleAtFixedRate(new TimerTask() {
```


----------



## mihe7 (10. Sep 2018)

fabipfolix hat gesagt.:


> Aber wieso tritt der Fehler dann auf sobald ich den Timer ein zweites Mal starte und nicht wenn ich ihn zum zweiten mal cancel.


1. Ein gecancelter Timer kann nicht neu gestartet werden. 
2. Beim Versuch, einen Task in einen gecancelten Timer zu "schedulen" tritt eine IllegalStateException auf (s. https://docs.oracle.com/javase/8/do...uleAtFixedRate-java.util.TimerTask-long-long-)


----------



## fabipfolix (10. Sep 2018)

Ah danke, lässt sich dann ja durch ein einfaches 
	
	
	
	





```
timer = new Timer();
```
 lösen


----------

