I just want to program a very simple clock using JavaFX. There are two version of this program. In the one I post here a Label displaying the date and time is updated. In the second instead a TextField is used to display the date and time and is updated. I use a FXML file (and the relative controller) and the Eclipse template. The point is that the following program (with a Label) does not work, while the same with a TextField yes. In my opinion and understanding they should both work or do not work.
The update is done every second by a thread.
Could someone help me to understand why it does not work and fix it? Many thanks!
Here is the main class of the program.
package application;
import java.time.LocalDateTime;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.fxml.FXMLLoader;
public class ClockLabel extends Application {
private boolean clockIsRunning = true;
@Override
public void start(Stage primaryStage) {
try {
HBox root = (HBox) FXMLLoader.load(getClass().getResource("view/ClockLabel.fxml"));
Scene scene = new Scene(root, 400, 400);
// scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.setTitle("A very simple clock");
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
clockIsRunning = false;
}
});
primaryStage.show();
// Starting the thread to update the clock...
Label clockDisplayLabel = (Label) root.getChildren().get(1);
updateClockLabel(clockDisplayLabel);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
private void updateClockLabel(Label label) {
Thread updateThread;
updateThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Clock update thread starts...");
while (clockIsRunning) {
LocalDateTime currentDateTime = LocalDateTime.now();
label.setText(printClockString(currentDateTime));
System.out.println("Ticking...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Clock update thread stops... and the program too...");
}
});
updateThread.start();
}
/**
* Given a LocalDateTime object, returns the string with the formatted time.
*
* @param dateTime
* A LocalDateTime object.
* @return A string with the formatted time.
*/
public static String printClockString(LocalDateTime dateTime) {
String dayOfWeek = dateTime.getDayOfWeek().toString();
int dayOfMonth = dateTime.getDayOfMonth();
String month = dateTime.getMonth().toString();
int year = dateTime.getYear();
String hour = String.format("%02d", dateTime.getHour());
String minute = String.format("%02d", dateTime.getMinute());
String second = String.format("%02d", dateTime.getSecond());
return dayOfWeek + ", " + dayOfMonth + " " + month + " " + year + ", " + hour + ":" + minute + ":" + second;
}
}
Here is the FXML file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<HBox alignment="CENTER" xmlns="http://javafx.com/javafx/8.0.152" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.view.ClockLabelController">
<children>
<Label alignment="TOP_LEFT" text="Current date:" />
<Label fx:id="clockDisplayLabel" alignment="CENTER" text="MONDAY, 19 FEBRUARY 2017, 19:49:40" />
<Button fx:id="stopClockButton" mnemonicParsing="false" onAction="#stopClock" text="Quit" />
</children>
</HBox>
Here is the controller (exacty the one generated by SceneBuilder)
package application.view;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
public class ClockLabelController {
@FXML // ResourceBundle that was given to the FXMLLoader
private ResourceBundle resources;
@FXML // URL location of the FXML file that was given to the FXMLLoader
private URL location;
@FXML // fx:id="clockDisplayLabel"
private Label clockDisplayLabel; // Value injected by FXMLLoader
@FXML // fx:id="stopClockButton"
private Button stopClockButton; // Value injected by FXMLLoader
@FXML
void stopClock(ActionEvent event) {
Platform.exit();
System.exit(0);
}
@FXML // This method is called by the FXMLLoader when initialization is complete
void initialize() {
assert clockDisplayLabel != null : "fx:id=\"clockDisplayLabel\" was not injected: check your FXML file 'Clock.fxml'.";
assert stopClockButton != null : "fx:id=\"stopClockButton\" was not injected: check your FXML file 'Clock.fxml'.";
}
}
The error I get is the following:
Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
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.ClockLabel$2.run(ClockLabel.java:55)
at java.lang.Thread.run(Thread.java:748)