2

So I followed this tutorial: https://www.youtube.com/watch?v=gyyj57O0FVI

and I made exactly the same code in javafx8.

public class CountdownController implements Initializable{

@FXML
private Label labTime;

@Override
public void initialize(URL location, ResourceBundle resources) {

    new Thread(){
        public void run(){
            while(true){
                Calendar calendar = new GregorianCalendar();
                int hour = calendar.get(Calendar.HOUR);
                int minute = calendar.get(Calendar.MINUTE);
                int second = calendar.get(Calendar.SECOND);
                String time = hour + ":" + minute + ":" + second;

                labTime.setText(time);
            }

        }
    }.start();
}

After I close the Window, application/thread is still running in the system. My guess its because the infinite loop, but shouldnt the thread be terminated with application closing?

Second thing is that when I try to set the text for Label I get the error:

Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364)
    at javafx.scene.Parent$2.onProposedChange(Parent.java:364)
    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$60(BehaviorSkinBase.java:197)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$$Lambda$144/1099655841.call(Unknown Source)
    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:143)
    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:146)
    at application.CountdownController$1.run(CountdownController.java:29)

...yes, I am going to read more about threads, but I would like to know the answer to these questions.

Tomasz Mularczyk
  • 34,501
  • 19
  • 112
  • 166

2 Answers2

3

Part I

A thread, when created, runs independent of other threads. You have a new thread which has an infinite loop, which implies, it will keep running forever, even after the stage has been closed.

Normally, using a infinite loop is not advised, because breaking out of it is very difficult.

You are advised to use :

You can then call either one of them (based on whatever you are using)

when your stage is closed. You can use something like :

stage.setOnCloseRequest(closeEvent -> {
       timertask.cancel();  
});  

JavaFX API's (thanks to James_D comment's)

These do not need to be explicitly canceled as ScheduledService uses daemon threads and AnimationTimer runs on the JavaFX thread.

Part II

Your second part of the question has been answered time and again in the forum.

You need to be on the JavaFX Application thread to use scene graph elements.

Since you have created a new thread and trying to update label, which is a JavaFX node, it throws the exception. For more information, please visit:

JavaFX error when trying to remove shape

Why am I getting java.lang.IllegalStateException "Not on FX application thread" on JavaFX?

Javafx Not on fx application thread when using timer

Community
  • 1
  • 1
ItachiUchiha
  • 36,135
  • 10
  • 122
  • 176
  • 1
    You can still use the long-running thread if you prefer; just call `setDaemon(true);` on it before starting it; then it will not prevent application closure. I would recommend using some kind of high-level scheduling API though; also see [`ScheduledService`](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/ScheduledService.html) and [`AnimationTimer`](http://docs.oracle.com/javase/8/javafx/api/javafx/animation/AnimationTimer.html). I would at least recommend your thread sleep for some time on each iteration, so it doesn't consume as much CPU as it can grab. – James_D Feb 20 '15 at 13:47
  • @James_D It just slipped off my mind that there are JavaFX API's as well. Thanks, I will add them to the answer. Though, `setDaemon(true)` is a solution, I wouldn't suggest user to use it without understanding what it does. – ItachiUchiha Feb 20 '15 at 13:56
  • so I can also declare `boolean close=false;` and `while(true && (close =! false){...` and `stage.setOnCloseRequest(closeEvent -> { close = true; }); ` Im gonna check the links tho. – Tomasz Mularczyk Feb 20 '15 at 14:59
  • 1
    No you can't as variables used inside lambdas must be `final` or `effectively final`. Though you may use AtomicBoolean for your use case, and you won't need `true` inside while. Just make sure you make the thread sleep for sometime to avoid unnecessary CPU usage as suggested by @James_D. Again, using a high level API, as suggested in the solution, is highly recommended. – ItachiUchiha Feb 20 '15 at 15:10
  • You *should* use `AtomicBoolean` in the above use case, to ensure liveness of the variable `close`, which is being accessed from multiple threads. But I really would not advise doing things this way. – James_D Feb 20 '15 at 15:15
  • 2
    If you use either `ScheduledService` or `AnimationTimer` there is no need to explicitly stop them. `ScheduledService` uses daemon threads by default, so they will not prevent application shutdown. `AnimationTimer` just executes on the FX Application Thread. I think for displaying a clock, I would use an `AnimationTimer`. – James_D Feb 20 '15 at 15:17
  • Ohh.. ScheduledService generates daemon thread, my bad. Got so much to learn!! Thanks @James_D – ItachiUchiha Feb 20 '15 at 15:33
  • @ItachiUchiha should I use `Platform.runLater(() -> ` to acces Label, or do You know better, maybe more proper solution? – Tomasz Mularczyk Feb 20 '15 at 15:49
  • @Tomek I would suggest to use a `ScheduledService` pass a Task to it, update the Task's message using [`updateMessage()`](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#updateMessage-java.lang.String-) and bind the Task's [`messageProperty()`](http://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html#messageProperty--) to the Label's [`textProperty()`](http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Labeled.html#textProperty--). – ItachiUchiha Feb 20 '15 at 16:33
2

With ScheduledExecutorService as far as I am concerned You cant easly set it as deamon and I don't want to play with stage.setOnCloseRequest(closeEvent -> {});

With AnimationTimer I cant do something like Thread.sleep(100) beetween iteration like you suggested because "AnimationTimer runs on the JavaFX thread."

ScheduledService is just quite difficult for me to understand right now...

so, as I was reading and reading about it I came to conclusion that maybe this simple option will be the best:

public class CountdownController implements Initializable{

@FXML
private Label labTime;
@FXML
private Button buttSTOP;

@Override
public void initialize(URL location, ResourceBundle resources) {
     Timer timer = new Timer(true); //set it as a deamon
     timer.schedule(new MyTimer(), 0, 1000);
}


public class MyTimer extends TimerTask{
    @Override
    public void run() {
        Calendar calendar = new GregorianCalendar();
        int hour = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);
        String time = hour + ":" + minute + ":" + second;

        Platform.runLater(() -> {
            labTime.setText(time);
        });

    }
}

Thanks James_D and ItachiUchiha. It works, let me know if I'am something missing!

EDIT: I also include code for Counting down the time, as it was my initial aim, maybe someone will find it usefull as well:

public class CountdownController implements Initializable{

@FXML
private Label labTime;
@FXML
private Button buttSTOP;

private Timer timer = new Timer(true); //set it as a deamon
private int iHours = 0,
            iMinutes = 1,
            iSeconds = 10;  


public void initCountdownController(int iHours, int iMinutes, int iSeconds){
    this.iHours = iHours;
    this.iMinutes = iMinutes;
    this.iSeconds = iSeconds;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
    buttSTOP.setOnAction(e -> {
        buttSTOPAction(e);
    });
    timer.schedule(new MyTimer(), 0, 1000);
}
private void buttSTOPAction(ActionEvent e) {
    timer.cancel();
}
public class MyTimer extends TimerTask{
    @Override
    public void run() {
        String time = iHours + ":" + iMinutes + ":" + iSeconds;
        Platform.runLater(() -> {
            labTime.setText(time);
        });

        if(iSeconds < 1)
            if(iMinutes < 1)
                if(iHours < 1)
                    this.cancel();
                else{
                    iHours--;
                    iMinutes = 59;
                    iSeconds = 59;
                }
            else{
                iMinutes--;
                iSeconds = 59;
            }
        else
            iSeconds--;
    }
}
Tomasz Mularczyk
  • 34,501
  • 19
  • 112
  • 166
  • It's really great to see somebody evaluate options, learn from them and come up with their own useful independent answer. – jewelsea Feb 20 '15 at 22:13