This question already has an answer here:
Update JavaFX Live nodes outside Application Thread
(1 answer)
Closed 2 years ago.
I'm in the process of teaching myself JavaFX and I recently stumbled upon "Platform.runLater", which should help you update the UI from inside a time consuming thread.
If I try to set any node inside the task thread, it won't let me because it's not running on the Application thread. HOWEVER, setting the progress of a progressbar works fine. Why is this? I've compared examples of use of the "runLater", and it does what it says until it comes to a "ProgressBar" (i.e, can't set labels or other nodes).
This works, as expected:
#Override
public void start(Stage primaryStage) throws Exception {
ProgressBar progressBar = new ProgressBar(0);
HBox hBox = new HBox(progressBar);
Scene scene = new Scene(hBox);
primaryStage.setScene(scene);
primaryStage.show();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
Platform.runLater(()->{
progressBar.setProgress(progressBar.getProgress()+0.1);
});
}
}
});
thread.setDaemon(true);
thread.start();
}
public static void main(String[] args) {
launch(args);
}
And this too, for unknown reason:
#Override
public void start(Stage primaryStage) throws Exception {
ProgressBar progressBar = new ProgressBar(0);
HBox hBox = new HBox(progressBar);
Scene scene = new Scene(hBox);
primaryStage.setScene(scene);
primaryStage.show();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
progressBar.setProgress(progressBar.getProgress()+0.1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
thread.setDaemon(true);
thread.start();
}
public static void main(String[] args) {
launch(args);
}
Why is this? I've compared both documentation and numerous examples...
In javafx can only manipulate UI elements such as the progressBar from within the dedicated platform thread. In your first example you do exactly this:
Platform.runLater(() -> {
progressBar.setProgress(progressBar.getProgress()+0.1);
});
which will execute `progressBar.setProgress(progressBar.getProgress()+0.1);ยด within the dedicated UI thread as soon it has free capacity (previous commands have been exectued on it).
In your second example you try manipulate it from your newly spawned thread which will lead to the Not on FX application thread ... exception.
Whenever you want to make changes on the UI elements within some new thread make sure you perform those within
Platform.runLater(() -> {
...
});
Related
When I was trying to implement popups, I did something like this:
Code snippet in OptionController:
static int SECOND = 100;
Timer timer = new Timer();
TimerTask task =
new TimerTask() {
#Override
public void run() {
SECOND--;
if (SECOND == 0) {
timer.cancel();
try {
AlertController.courtChangeAlert("Time's up."); // line 56
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
public void clickTimerStart() throws IOException {
timer.scheduleAtFixedRate(task, 1000, 1000);
}
Code snippet in AlertController:
public static void courtChangeAlert(String alert) throws IOException {
FXMLLoader fxmlLoader =
new FXMLLoader(AlertController.class.getResource("/view/CourtChangeAlert.fxml"));
showScene(fxmlLoader, alert); // line 43
}
private static void showScene(FXMLLoader fxmlLoader, String alert) throws IOException {
AnchorPane alertPane = fxmlLoader.load();
AlertController alertController = fxmlLoader.getController();
alertController.setAlertLabel(alert);
Scene scene = new Scene(alertPane);
Stage stage = new Stage(); // line 51
stage.setScene(scene);
stage.show();
}
After the countdown ends, I got error message:
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.stage.Stage.<init>(Stage.java:241)
at javafx.stage.Stage.<init>(Stage.java:227)
at controller.AlertController.showScene(AlertController.java:51)
at controller.AlertController.courtChangeAlert(AlertController.java:43)
at controller.OptionController$1.run(OptionController.java:56)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
What am I doing wrong and how can I modify the code? Thanks in advance.
Just wrap the line AlertController.courtChangeAlert("Time's up."); so that it is started on the JavaFX thread.
Platform.runLater(()->AlertController.courtChangeAlert("Time's up."));
The timer has it's own thread, and when it fires the action it fires it on it's own thread and not javafx's thread. That is the error you're getting.
Long stories short, a Timer() starts a new process, which is incapable of popping up your new window. You will need to invoke Platform.runLater() to popup your window in the FX thread. And you will have to wrap your function as a Runnable() in turn.
I have a splash screen :
I need to have the animation of the progress bar (Indeterminate) but it doesn't work.
It's maybe due to because my thread is running in my initilize methode.
public class splashscreenController implements Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
new SplashScreen().run();
}
class SplashScreen extends Task {
#Override
public Object call() {
Platform.runLater(new Runnable() {
#Override
public void run()
Parent root = null;
try {
Thread.sleep(3000);
root = FXMLLoader.load(getClass().getResource("../gui/NewUI.fxml"));
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
Stage stage = new Stage();
stage.initStyle(StageStyle.UNDECORATED);
assert root != null;
Scene scene = new Scene(root, 1280, 720);
stage.setScene(scene);
stage.show();
MainJavaFx.setPrimaryStage(stage);
((Stage) panParent.getScene().getWindow()).close();
}
});
return null;
}
}
}
There are 2 issues in your code:
new SplashScreen().run();
A Task does not provide functionality for running on a new thread. run is executed on the calling thread.
class SplashScreen extends Task {
#Override
public Object call() {
Platform.runLater(new Runnable() {
#Override
public void run() {
// placeholder for parts of your code
longRunningOperation();
guiUpdate();
}
});
return null;
}
}
Even if you execute this task on a seperate thread, the Runnable passed to Platfrom.runLater is executed on the JavaFX application thread and doing long-running operations from this runnable freezes the GUI.
Do all the long-running operations on the background thread instead and only do short updates using Platfrom.runLater.
new Thread(new SplashScreen()).start();
class SplashScreen extends Task {
#Override
public Object call() throws IOException, InterruptedException {
Thread.sleep(3000);
final Parent root = FXMLLoader.load(getClass().getResource("../gui/NewUI.fxml"));
Platform.runLater(new Runnable() {
#Override
public void run() {
Stage stage = new Stage();
stage.initStyle(StageStyle.UNDECORATED);
Scene scene = new Scene(root, 1280, 720);
stage.setScene(scene);
stage.show();
MainJavaFx.setPrimaryStage(stage);
((Stage) panParent.getScene().getWindow()).close();
}
});
return null;
}
}
Note that since you're not using the functionality provided by Task, you could simply implement Runnable with your class instead of inheriting from Task.
I know there are million questions about updating UI in javafx from differents thread, but in my case I am working with one scene only and I would like interrupt a loop while and then I start another thread that will update UI.
I'm testing the following code :
Class main :
public static void main(String[] args)
{
Application.launch(args);
}
#Override
public void start(Stage stage) {
stage.setTitle("My project");
Group root = new Group();
Scene scene = new Scene(root, 800,600,Color.LIGHTBLUE);
Label lab = new Label("This is a test");
root.getChildren().add(lab);
stage.setScene(scene);
stage.show();
GuiUpdate gu=new GuiUpdate(stage,root);
int i=0;
while(i<5)
{
synchronized (this) {
gu.getThread().start();
try {
wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
i++;
}
}
my class GuiUpdate, I have 2 button.
First button execute an action.
Second Button stop this Thread and set previous Scene property.
public GuiUpdate(Stage stage, Parent root){
this.root=new Group();
thread = new Thread(this);
oldRoot=root;
this.stage=stage;
}
#Override
public void run()
{
Runnable runner = new Runnable() {
#Override
public void run() {
System.out.println("position 3 : runner");
Button[] button=new Button[2];
button[0]=new Button("Action");
//Set action for button 0
button[1]=new Button("Back");
button[1].setOnAction(e -> {
notify();
stage.getScene().setRoot(oldRoot);
getThread().interrupt();
});
root.getChildren().addAll(oldRoot,button[0],button[1]);
stage.getScene().setRoot(root);
}
};
if (Platform.isFxApplicationThread()) {
System.out.println("Position 1");
runner.run();
} else {
System.out.println("Position 2");
Platform.runLater(runner);
}
}
public Thread getThread(){return thread;}
}
Problem :
When I run project, Platform.runLater(runner) seems not to be working and Application freeze because "main Thread" is waiting
I have a JavaFX with TabPane which holds Java Objects with data into different tabs. I found that when the content of the tab takes time to load because there are SQL queries for execution the application just hangs. Is there any way to display some "Loading" message during the content utilization? for example:
Tab.setContent(<some_heavy_Java_Object>);
Is there any workaround to solve this in JavaFX or Java?
P.S I tested this code sample but I get error when I try to run the code:
TabContentInfrastructure content;
class GetDailySalesService extends Service<ObservableList<Object>>
{
#Override
protected Task createTask()
{
return new GetDailySalesTask();
}
}
class GetDailySalesTask extends Task<ObservableList<Object>>
{
#Override
protected ObservableList<Object> call() throws Exception
{
content = new TabContentInfrastructure();
return (ObservableList<Object>) content.initTestTabContentData();
}
}
..........
VBox vbox = new VBox();
content = new TabContentInfrastructure();
vbox.getChildren().add(content.initTestTabContentData());
GetDailySalesService service = new GetDailySalesService();
Region veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
ProgressIndicator p = new ProgressIndicator();
p.setMaxSize(140, 140);
p.progressProperty().bind(service.progressProperty());
veil.visibleProperty().bind(service.runningProperty());
p.visibleProperty().bind(service.runningProperty());
//tableView.itemsProperty().bind(service.valueProperty());
StackPane stack = new StackPane();
stack.getChildren().addAll(vbox, veil, p);
service.start();
tabdata.setContent(stack);
Can you help me to solve this issue.
Another attempt to solve the issue:
Task<VBox> task = new Task<VBox>()
{
#Override
protected VBox call() throws Exception
{
TabContentInfrastructure content = new TabContentInfrastructure();
return content.initTestTabContentData();
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
Region veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
ProgressIndicator p = new ProgressIndicator();
p.setMaxSize(140, 140);
//p.progressProperty().bind(service.progressProperty());
veil.visibleProperty().bind(task.runningProperty());
p.visibleProperty().bind(task.runningProperty());
//vb.visibleProperty().bind(service.runningProperty().not());
//tableView.itemsProperty().bind(service.valueProperty());
StackPane stack = new StackPane();
task.setOnSucceeded(new EventHandler<WorkerStateEvent>()
{
#Override
public void handle(WorkerStateEvent t){
System.out.print("Entered setOnSucceeded**********" + t.getSource().getValue());
stack.getChildren().clear();
stack.getChildren().addAll(task.getValue());
}
});
stack.getChildren().addAll(veil, p);
tabdata.setContent(stack);
This time the result is null.
And another unsuccessful attempt.
StackPane stack = new StackPane();
Region veil = new Region();
ProgressIndicator p = new ProgressIndicator();
Task<VBox> task = new Task<VBox>()
{ // create new task
#Override
public VBox call() throws InterruptedException
{
Platform.runLater(new Runnable()
{ // USE THIS INSTEAD
#Override
public void run()
{
try
{
// ui updates here(inside application thread)
// this is needed if you want to update your ui
// you cannot update any ui from outside the application thread
TabContentInfrastructure content = new TabContentInfrastructure();
//stack.getChildren().clear();
stack.getChildren().addAll(content.initTestTabContentData());
}
catch (InterruptedException ex)
{
//Logger.getLogger(InfrastructureDataTabs.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
return null;
}
};
new Thread(task).start();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
p.setMaxSize(140, 140);
p.progressProperty().bind(task.progressProperty());
veil.visibleProperty().bind(task.runningProperty());
p.visibleProperty().bind(task.runningProperty());
//vb.visibleProperty().bind(service.runningProperty().not());
//tableView.itemsProperty().bind(service.valueProperty());
stack.getChildren().addAll(veil, p);
tabdata.setContent(stack);
you must load the data in a different Task Thread, I see that you are trying to do the same. The problem with your code is that you are not updating your progress bar. You must use updateProgress as shown here
http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm#BABGJIDB
Here is a very nice example from Jewelsea where he has very nicely displayed the use of Task and how to use it to update the progress on the UI
Update progress bar and multiple labels from thread
Here you can find out how to use the Task as well as update the UI from the task
Some more Nice examples are
https://community.oracle.com/message/9927179#9927179
https://community.oracle.com/message/10631701#10631701
You should just execute the expensive computations in another thread and then update e.g. a progresss bar in the javafx application thread.
Also your application wont hang during the process anymore.
Like this:
Task task = new Task<Void>() { // create new task
#Override
public Void call() {
// do expensive computations here
Platform.runLater(new Runnable() { // return to application thread
#Override
public void run() {
// ui updates here(inside application thread)
}
});
return null;
}
};
new Thread(task).start(); // execute task in new thread
Hope it helps, Laurenz.
EDIT -------------
Task<Void> task = new Task<Void>() { // create new task
#Override
public Void call() {
try {
Thread.sleep(50); // this simulates expensive computations(in your case loading) - your app would hang for this duration
} catch (InterruptedException e) {
e.printStackTrace();
}
// REMOVE THE SLEEP AND PUT YOUR TASK HERE
// Main.this.root.setPrefHeight(50); // would NOT work(because outside application thread)
Platform.runLater(new Runnable() { // USE THIS INSTEAD
#Override
public void run() {
// ui updates here(inside application thread)
// this is needed if you want to update your ui
// you cannot update any ui from outside the application thread
}
});
return null;
}
};
new Thread(task).start(); // execute task in new thread
We have a JDialog that contains a single stage with multiple scenes. Each scene has a next button. When the user clicks "next" we call stage.setScene. This works fine on Windows and Linux, but on Mac, setScene never returns. The application hangs, and it appears to be a thread deadlock. Here is a sample app that reproduces the problem. We have also tried various java 7 builds and java8 pre-release. We think we have found a couple workarounds, but we would like to understand why this code has problems on Mac. Sample app with bug:
public class SampleFxInsideSwing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new SampleFxInsideSwing().initFX();
}
});
}
private void initFX()
{
JDialog dialog = new JDialog();
dialog.setSize(new Dimension(500,500));
final JFXPanel stage = new JFXPanel();
dialog.add(stage);
Platform.runLater(new Runnable()
{
#Override
public void run()
{
Button nextButton = new Button("Next");
nextButton.setOnAction(new EventHandler<ActionEvent>()
{
//#Override
public void handle(ActionEvent event)
{
try
{
System.out.println("Clicked");
AnchorPane parent = FXMLLoader.load(SampleFxInsideSwing.class.getResource("SampleFxml.fxml"));
Scene secondScene = new Scene(parent);
stage.setScene(secondScene);
System.out.println("Displaying second scene!");
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
Group group = new Group();
group.getChildren().add(nextButton);
Scene scene1 = new Scene(group);
stage.setScene(scene1);
}
});
dialog.setVisible(true);
}
}