JavaFX- new Stage in a task - java

I want to open a new window from the task but for some reason after line Stage stage = new Stage the code stops executing, but there is no error.
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("sample2.fxml"));
Stage stage = new Stage();
System.out.println("Print");
stage.setTitle("My New Stage Title");
stage.setScene(new Scene(root, 100, 100));
stage.show();
return null;
}
};
It never prints out the message 'Print'.

Answer to Question
The reason your Task is failing is because you are creating a Stage on a thread other than the JavaFX Application Thread. The Javadoc of Stage states:
Stage objects must be constructed and modified on the JavaFX Application Thread.
This means when you attempt to create a Stage on the background thread that the Task is running on it will result in an IllegalStateException with a message telling you that you aren't on the JavaFX Application Thread. To solve this issue wrap all code that creates and/or modifies a Stage in a Platform.runLater(Runnable) call.
Side Note: It would probably be better to not create the Stage in the Task at all. Rather, in your case, simply return the result of FXMLLoader.load(URL) and create the Stage when handling the success of the Task.
Task<Parent> task = new Task<Parent>() {
#Override
protected Parent call() throws Exception {
return FXMLLoader.load(getClass().getResource("sample2.fxml"));
}
};
task.setOnSucceeded(event -> {
Parent root = task.getValue();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.show();
};
Why No Error Shown?
You say there is no error but you also don't show any code that would display an error if one does occurr. When a Task fails it sets the cause of failure in the exception property. To handle the case when a Task fails you can:
Listen to the exception property
Add an EventHandler to handle a WorkerStateEvent.WORKER_STATE_FAILED event and query the exception property
Either using task.setOnFailed(EventHandler) or task.addEventXXX(EventType, EventHandler) where XXX is either Filter or Handler
Override the protected void failed() method in your Task implementation and query the exception property
The failed() method will always be called on the JavaFX Application Thread
Catch and handle the exception in the call() method before re-throwing it
Possibly other ways I'm not currently thinking of

You need an Executor to start the thread
Executor exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t;
});
exec.execute(task);

Related

Not on FX application thread; currentThread = AWT-EventQueue-0

So I'm trying to make my JavaFX Application visible, if I press CTRL + Alt + D (I'm using jkeymaster). But everytime I write Stage.show(); in my HotKeyListener I get Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0 (line 7) (I also tested to show a file chooser in my hot key listener and outside of the listener and if I do the second thing I get no error). And also if I just put System.out.println("Test") in my hot key listener without the other things it just outputs it and I get no error
public class Main extends Application {
public static Stage s;
#Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(FXMLLoader.load(getClass().getResource("Main.fxml")));
stage.setScene(scene);
stage.setTitle("Test");
stage.setResizable(false);
s = stage;
}
}
Controller:
public class Controller {
public void initialize() {
Provider provider = Provider.getCurrentProvider(true);
openSaveDialog(Main.s); //No error
HotKeyListener l = hotKey -> {
Main.s.show();
openSaveDialog(Main.s);
//Returns an error
};
provider.register(KeyStroke.getKeyStroke("control alt D"), l);
}
public File openSaveDialog(Stage s) {
FileChooser chooser = new FileChooser();
chooser.setTitle("Select the output");
return chooser.showSaveDialog(s);
}
}
If you try wrapping the contents of the HotKeyListener in a call to Platform.runLater() this should fix it. Since you're modifying the JavaFX Scene graph, this work must be done on the Application thread.
HotKeyListener l = hotKey -> {
Platform.runLater(() -> {
Main.s.show();
openSaveDialog(Main.s);
});
};
Although, instead of using the AWT HotKeyListener, you should really register a key listener through JavaFX's events. Then you wouldn't need to call Platform.runLater()

change color of disableProperty() function / how disable clicking on Stage

Hello it's my first ask on stack. I got problem with disablebleProperty() function. It make my Stage dark grey and it's looking terrible. How can I disable clicking on my stage when some Task is running like in the following code:
buttonName.setOnAction((event) -> {
Task task = new Task() {
#Override
protected Integer call() throws Exception {
try {
buttonName.getScene().setCursor(Cursor.WAIT);
buttonName.getScene().getRoot().disableProperty().bind(primaryStage.getScene().cursorProperty().isEqualTo(Cursor.WAIT));
anotherFunction();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
buttonName.getScene().setCursor(Cursor.DEFAULT);
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
});
You should not update the ui from a thread other than the JavaFX application thread. Since Task.call is run on a seperate thread, you shouldn't run the code there. Also binding to the curser property instead of also calling setDisable(false) seems like a bad idea, especially since you never unbind the property.
Use the event handler for onSucceeded for handling the successful completion of the task. (onFailed and onCanceled are available for different ways your task could finish, which won't happen in the code snippet you posted.)
buttonName.setOnAction((event) -> {
buttonName.getScene().setCursor(Cursor.WAIT);
buttonName.setDisable(true);
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
anotherFunction();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
};
task.setOnSucceeded(evt -> {
buttonName.getScene().setCursor(Cursor.DEFAULT);
buttonName.setDisable(true);
});
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
});
To disable other controls in the scene too, you could instead disable/reenable the root of the scene:
buttonName.getScene().getRoot().setDisable(newValue);
To prevent the grayed out look JavaFX applies on the disabled controls (setting opacity to 0.4) you could apply a different opacity with higher precedence e.g. by setting the opacity from code
buttonName.setOpacity(1);
or by applying a style using a custom stylesheet
style.css
*:disabled {
-fx-opacity: 1;
}
scene initialisation
scene.getStylesheets().add("style.css");

JAVAFX how to update GUI elements (nodes)

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() :)

JavaFX Stage shows empty scene

I've been trying to open a new Window in order to display a progress bar, from a controller :
Stage fenetre = new Stage();
fenetre.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader;
Parent root;
Scene chargementBox;
loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/views/Chargement.fxml"));
loader.load();
root = loader.getRoot();
chargementBox = new Scene(root);
fenetre.setTitle("Chargement");
fenetre.resizableProperty().set(false);
fenetre.setScene(chargementBox);
fenetre.show();
It shows the window. But it's empty :
This is what it should show
This is what i got instead
I tried everything, I used other FXML files, the window sizes are correct but it's always empty. The same code works on other Controllers, but not here.
Help please. There is no exceptions and no errors. Thank's
Edit : I've found the reason why it doesn't show, it's because later in the code i have this function : Transport.send(message); that blocks the program from refreshing my scene and displaying the elements. Is there a way i can run that line in the background or in another thread (I never used threads before.) Thank's again for the help.
To run something on the background thread you need to either use a task (useable once) or a service (reusable).
This is how you can use a service:
Service<Void> service = new Service<Void>(){
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
//do your logic here.
return null;
}
};
}
};
service.setOnSucceeded(event -> {
//do some processing when complete
});
service.setOnFailed(event -> {
//do some processing when it failes
});
service.start();

Building Multithreading ProgressBar in JavaFx

I am attempting to build a progress bar that is being updated as my application is retrieving and populating data to the GUI. I figured that the progress bar will be reused a lot so I decided to create its own class. However, I don't believe I understand either the Worker/Task or Multi-Threading in general to create a re-usable situation. What would be the recommended approach to creating a progress bar that can listen to the application thread and update the bar accordingly. Here is my attempt:
// Simple Progress Bar View as Pop Up
public class ProgressIndicatorUtil{
#FXML
private ProgressBar progressBar;
#FXML
private Label statusLabel;
#FXML
private Button closeButton;
#FXML
private Label valueLabel;
private Worker worker;
private Stage stage;
public void setPopUpStage(Stage stage) {
this.stage = stage;
}
public void setWorker(Worker worker) {
this.worker = worker;
}
public void setLinkToMainPage(Object controller) {
((Task<String>) worker).setOnSucceeded(event -> stage.close());
((Task<String>) worker).setOnCancelled(event -> {
closeButton.setVisible(true);
stage.requestFocus();
statusLabel.setTextFill(Color.RED);}
);
valueLabel.textProperty().bind(Bindings.format("%5.1f%%", worker.progressProperty().multiply(100)));
progressBar.progressProperty().bind(worker.progressProperty());
statusLabel.textProperty().bind(worker.messageProperty());
}
#FXML
private void handleClose(ActionEvent e){
stage.close();
}
}
The Controller that calls the View Pop-Up and runs the Progress Bar Thread.
public class MyController{
//Controller calling the view and disabling the main GUI
private void loadProgressBar(Worker worker){
try{
FXMLLoader loader = new FXMLLoader(getClass()
.getClassLoader().getResource("main/resources/fxml/ProgressBar.fxml"));
AnchorPane pane = (AnchorPane)loader.load();
Stage popUpStage = new Stage();
popUpStage.initModality(Modality.WINDOW_MODAL);
Scene scene = new Scene(pane);
popUpStage.setScene(scene);
ProgressIndicatorUtil controller = loader.getController();
controller.setPopUpStage(popUpStage);
controller.setWorker(worker);
controller.setLinkToMainPage(this);
mainPane.setDisable(true);
popUpStage.showingProperty().addListener((obs, hidden, showing) -> {
if(hidden) mainPane.setDisable(false);});
popUpStage.show();
}catch(IOException e){
e.printStackTrace();
}
}
private void runProgressBar(Worker worker) {
new Thread((Runnable) worker).start();
}
//A user action that runs the progress bar and GUI
#FXML
private void aBigProcessingEvent(ActionEvent event) {
Worker worker = new Task<String>(){
#Override
protected String call() throws Exception {
updateProgress(0, 3);
updateMessage("Clearing Data");
processingEvent01();
updateProgress(1, 3);
updateMessage("Retriving Data And Adding To List");
processingEvent02();
updateProgress(2, 3);
updateMessage("Populating Data");
processingEvent03();
updateProgress(3, 3);
return "Finished!";
}
};
loadProgressBar(worker);
runProgressBar(worker);
}
}
The program works fine, visually, but it throws an Exception like this (Not On FX Application Thread) and running Platform.runLater() on my "processingEvent" methods will cause my progress bar to be 100% immediately, but it won't throw anymore Exceptions. Any suggestion to how to split the application modification methods and the worker methods apart while keeping the progression connected to the processingEvent methods? Much thanks.
There is nothing wrong with the (incomplete) code you have posted, so there errors are in other parts of your code. Since the code is incomplete, I have to make some educated guesses as to what is happening. (Note: it is actually much better if you can create complete examples when you post questions, so that you ensure the cause of the issue you are asking about is included.)
Since you are getting an IllegalStateException "Not on the FX Application Thread", you must be updating the UI from a background thread. Since the only code you've posted that runs in a background thread is in the Task you create in aBigProcessingEvent(), the UI updates must be happening in the one or more of the processingEventXX() methods you haven't shown.
If you wrap the calls to processingEventXX() in Platform.runLater(), then you won't see any progressive updates to the progress bar. Platform.runLater() schedules the runnable you provide to be executed on the FX Application Thread and exits immediately. There is no other code in the Task that takes time to run, so the entire task is completed in very little time, and by the time the FX Application Thread renders the next frame, the task is complete and the progress property is at 1.
So presumably your processingEventXX() methods take time to execute, and also update the UI. You must wrap the calls that update the UI in those methods in Platform.runLater(...). The code wrapped in Platform.runLater(...) must not include code that takes a long time to run. I.e. they should look like
private void processingEvent01() {
// some long-running process here...
Platform.runLater(() -> {
// update UI here....
});
// some other long-running process here (perhaps)
}

Categories