I have problem, when I want add Node to my GUI from other Thread. It throws IllegalStateException and I don't know how to fix it.
public class DashBoardController implements Initializable {
#FXML
private FlowPane dashBoardPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
try {
RTMClientV2 client = new RTMClientV2("localhost", 9009, new DashBoardArranger(this));
Thread clientTH = new Thread(client);
clientTH.start();
} catch (IOException ex) {
Logger.getLogger(DashBoardController.class.getName()).log(Level.SEVERE, null, ex);
}
}
public synchronized void addToDashBoard(Pane root){
dashBoardPane.getChildren().add(root);
}
}
I just load my .FXML file to GUI with this controller and when program starts it runs Thread responsible for communication with server (clientTH.start();) and everything is OK. But when server send data after init. and I want this data add to my Dashboard, I use method public synchronized void addToDashBoard(Pane root) as before, but it throws java.lang.IllegalStateException and I have no idea why.
btw: I have found this: "If this Parent node is attached to a Scene, then its list of children must only be modified on the JavaFX Application Thread. An IllegalStateException is thrown if this restriction is violated.", but it is not useful for me. Dashboard is added to another Pane in my GUI.
btw: Output:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(Unknown Source)
at javafx.scene.Parent$1.onProposedChange(Unknown Source)
at com.sun.javafx.collections.VetoableObservableList.add(Unknown Source)
at com.sun.javafx.collections.ObservableListWrapper.add(Unknown Source)
at probeobserver.gui.probeSite.DashBoardController.addToDashBoard(DashBoardController.java:125)
at probeobserver.gui.probeSite.DashBoardArranger.setCompName(DashBoardArranger.java:66)
at probeobserver.rtm.RTMClientV2.readAllDataAndUpdate(RTMClientV2.java:144)
at probeobserver.rtm.RTMClientV2.run(RTMClientV2.java:80)
at java.lang.Thread.run(Thread.java:722)
In your I/O thread, you need to interact with the UI within the UI thread:
Platform.runLater(new Runnable() {
#Override
public void run() {
Pane root = ...; //if you set any properties of the pane, do it here.
dashBoardController.addToDashBoard(root);
}
}
Related
Using this approach I am trying to implement an application preloader for my JavaFX application. I want to load some heavy stuff in init() which may throw an exception and then continue with start(). To handle the exceptions I am showing an alert using new Alert(AlertType.ERROR).showAndWait(); that shows some details to the user.
public class Test extends Application {
#Override
public void init() throws Exception {
try {
// dome some heavy stuff here
throw new Exception();
} catch (Exception e) {
new Alert(AlertType.ERROR).showAndWait();
Platform.exit();
}
}
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
But this results in the alert not showing up and generating the following stack trace (see full stacktrace here):
Exception in Application init method
java.lang.reflect.InvocationTargetException
...
Caused by: java.lang.RuntimeException: Exception in Application init method
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:895)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.base/java.lang.Thread.run(Thread.java:830)
Caused by: java.lang.IllegalStateException: Not on FX application thread; currentThread = JavaFX-Launcher
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
...
at javafx.controls/javafx.scene.control.Alert.<init>(Alert.java:222)
at src/gui.Test.init(Test.java:18)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:824)
... 2 more
Exception running application gui.Test
However my approach works fine if I move the howl code from init() to start().
In the past I've been able to solve this kind of issues with wrapping the logic in a Platform.runLater() call. ie:
try {
[...]
} catch (Exception e) {
Platform.runLater(new Runnable() {
#Override public void run() {
new Alert(AlertType.ERROR).showAndWait();
Platform.exit();
});
}
I use this approach every time I need to do some work that's not directly related to what's going on in the interface.
As taken from the wiki :
public static void runLater(Runnable runnable)
Parameters:
runnable - the Runnable whose run method will be executed on the JavaFX Application Thread
This question is already asked but i copuldnt udnerstand it. Imagine that. I have a program with 2 scenes. First scene1 is opened which is the scene for connection to database. There is a label called Status which should turn from "Disconnected" to "Connected" when the connection is established(by clicking the COnnect button). So i made a function to take the button "Connect" onClick event. This function is declared and defined inside a controller class (i am using fmxl designing with scene builder). So basicly i want to change the status to "COnnected" (status.setText("Connected")) from the connection function(method) which is inside the controller class. However when I do that, the text isn't changed instantly after the connection is established, but it changes when the scene is about to close and i am about to change the scene to the new one... I read on the internet and i saw that i should use Platform.runLater and threading so i tried:
private void changeSC() throws IOException, InterruptedException, SQLException
{
dbConnect();
Thread thrd = new Thread() {
public void run() {
Platform.runLater(new Runnable() {
#Override public void run() {
status.setText("Connected");
status.setTextFill(Color.GREEN);
}});
}
};
thrd.start();
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Thread.sleep(2000);
Main.window.setScene(primary);
}
changeSC is the function that is executed when Connect button is clicked. This is my old version which also doesnt work:
private void changeSC() throws IOException, InterruptedException, SQLException
{
dbConnect();
status.setText("Connected");
status.setTextFill(Color.GREEN);
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Thread.sleep(2000);
Main.window.setScene(primary);
}
The problem is with the text which should change to "Connected". It changes just when my scene is about to be switched....
You need to use a Task, if you have some long running operation. Otherwise when that operation is called from the JavaFX Application Thread it would block the GUI.
If you want to update the GUI from the Task you have to use Platform.runlater, which will run the code on the JavaFX Application Thread:
Platform.runlater
Updates to the Nodes of your GUI have always to be performed on the JavaFx Thread.
When you update status inside the Listener of button it should work.
button.setOnAction(evt -> {
dbConnect();
status.setText("Connected");
// ...
});
If dbConnect() takes some time, you can use a Task:
Task<Void> longRunningTask = new Task<Void>() {
#Override
protected Void call() throws Exception {
dbConnect();
Platform.runLater(() -> status.setText("Connected"));
return null;
}
};
button.setOnAction(e -> {
new Thread(longRunningTask).start();
});
Since you are connecting to a database, you should put this code to run in background using Task or a Service to keep the GUI Thread responding to the user inputs. Just remember that only in the GUI Thread you can update the view state (changing the value of a text in your case). You can use a java Thread and use Platform.runLater which means that the code inside is schedule to be precessed by the GUI Thread but in your case you are using in the wrong way. First the logic to connect to the database should be inside the method run of the thread and once the method finish, set the value of the text and do whatever you want after. Also you'll want to show the new Scene when all the process has been finished to get a chance to the user to see the change in the text. You can change your code in this way:
private void changeSC() throws IOException, InterruptedException, SQLException
{
Thread thrd = new Thread() {
public void run() {
dbConnect();
Platform.runLater(new Runnable() {
#Override public void run() {
status.setText("Connected");
status.setTextFill(Color.GREEN);
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Main.window.setScene(primary);
}
});
}
};
thrd.start();
}
If you choose use a Task you dont have to deal with Platform.runLater explicitly. You only need to create a task (an implementation of the class Task), wrap this inside a java Thread, start it and the set a handler for the different events (eg: setOnSucceeded). This is your code using Task:
private void changeSC() throws IOException, InterruptedException, SQLException
{
Task<Void> task = new Task<Void>(){
#Overrdie
protected Void call() {
dbConnect();
return null;
}
};
//start Task
Thread t = new Thread(task);
t.setDaemon(true); // thread will not prevent application shutdown
t.start();
task.setOnSucceeded(event -> {
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Main.window.setScene(primary);
});
}
ok i fixed it by setting task.SetonFailed() :)
I have created a JavaFX application, and noticed that after I close the main stage, the following happens:
The Applications "stop" method is called
The main method continues
After the program leaves the main method, the JVM can not close
I do not create any threads (explicitly not, at least). The threads that are running at this point are (from the debug console):
InvokeLaterDispatcher
Prism Font Disposer
'pool-2-thread-1' (I don't know what this is - ThreadPoolExecutor parts are in its stacktrace)
HSQLDB Timer (I'm using a HSQLDB file db on the development/debug system)
FX Access Thread (Visual Debugger)
Abandoned connection cleanup thread
Which of these threads can stop the JVM from closing? I would think that all of these should be daemon threads...
Here is my Application code:
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root);
scene.getStylesheets().add("/styles/Styles.css");
//... Scene/stage setup here
stage.show();
}
#Override
public void stop() throws Exception {
super.stop();
}
public static void main(String[] args) {
launch(args);
}
}
Adding a System.exit(0); on the end of the stop() method fixes this, but I'm not sure if this is the best solution to the problem...
Any ideas?
Thx in advance
You say you are using a HSQLDB, you don't show the code where you initialize it. In your stop() method, close that connection.
I'm having problem to close my javaFX application, when I click the close button from my stage, my application disappears but if I look for it in my task manager my application still there without close.
I've tried to use this code below to force it close the main thread and all childrens threads but the problem persists.
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent t) {
Platform.exit();
}
});
Does your application spawn any child threads? If so have you ensured that you terminate them (assuming that they're not daemon threads)?
If your application spawns non-daemon threads then they (and therefore your app) will continue to live on until such time you kill the process
The only way was to call System.exit(0);
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent t) {
Platform.exit();
System.exit(0);
}
});
[EDITED]
System.exit will just hide your application, if you open SO's manager task your application will be there. The correct way is to check your Threads, one by one and close all before close application.
First Look Here
public void start(Stage stage) {
Platform.setImplicitExit(true);
stage.setOnCloseRequest((ae) -> {
Platform.exit();
System.exit(0);
});
}
I currently had this problem while using an ThreadExecutor in the controller.
Application does not exit if the ThreadExecutor is not shutdown.
See here:
how-to-shut-down-all-executors-when-quitting-an-application
As it can be a problem to recognize an application exit in the controller, you can get a reference to the controller from your Application class like so (using the sample application from Eclipse):
public class Main extends Application {
private SampleController controller;
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
BorderPane root = (BorderPane)loader.load(getClass().getResource("Sample.fxml").openStream());
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
controller = loader.<SampleController>getController();
}
catch(Exception e)
{
e.printStackTrace();
}
}
Your Application overrides the stop method, where you can call a housekeeping method of the controller (i use a method called startHousekeeping):
/**
* This method is called when the application should stop,
* and provides a convenient place to prepare for application exit and destroy resources.
*/
#Override
public void stop() throws Exception
{
super.stop();
if(controller != null)
{
controller.startHousekeeping();
}
Platform.exit();
System.exit(0);
}
I was able to fix this problem by calling com.sun.javafx.application.tkExit(). You can read more in my other answer here: https://stackoverflow.com/a/22997736/1768232 (these two questions really are duplicates).
Just a note:
Try checking if you use
Platform.setImplicitExit(false);
Had a similar problem and overflowing my tasks. The above line will not make the stage close, it will hide it.
To imitate pressing 'x' one can do:
stage.fireEvent(new WindowEvent(stage, WindowEvent.WINDOW_CLOSE_REQUEST))
Im trying javafx for the first time. In my Model i hava a property that tells if my app is connected or not. There is a connectionListener somewhere else that calls connection.setConnectionState(state) when the value changes.
Problem is i got Exception:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
This makes sense, as i attempted to change the UI in a thread that wasnt an UI-thread. So i added Platform.runLater(..) to my setter and it works.
Question: My setters will get very ugly if i have to do this for every property. Is there some nice/correct way to this in javafx?
Model:
public class Connection {
private final StringProperty connectionStateProperty = new SimpleStringProperty();
public StringProperty getConnectionStateProperty() {
return connectionStateProperty;
}
public void setConnectionState(final ConnectionState connectionState) {
Platform.runLater(new Runnable() {
#Override
public void run() {
connectionStateProperty.setValue(connectionState.toString());
}
});
}
}
Controller:
public class ConnectionController implements Initializable {
#FXML
Label connectionLabel;
#Override
public void initialize(URL location, ResourceBundle resources) {
Bindings.bindBidirectional(connectionLabel.textProperty(),
connection.getConnectionStateProperty());
}
}
In the fx-guice project there is a method annotation called #FxApplicationThread which will run the method on the FX thread as long the object was injected via guice, I've found this really easy to use and clean.