Updating JavaFX Controls While Running the App - java

I am using JavaFx for creating a Java Standalone Application.
I have seen some examples but I am not able to understand how to use the javaFX Task in my code scenario.
This is the Controller function which I am calling for Button onAction which I have set from SceneBuilder -->
public class MainScreenController {
#FXML
private JFXButton btnSelectImg;
#FXML
private ImageView imageViewObj;
#FXML
private ProgressBar progressBarObj;
//..
//..
#FXML
private void onFileSelectButtonClick() {
//Some Operations are carried out
//..
//Then I want to set Image in ImageView
imageViewObj.setImage(myImage);
// Some Code Here
//..
// Set Progress
progressBarObj.setProgress(0.1);
// Some Code Here
//..
// Set Progress
progressBarObj.setProgress(0.2);
//...
//...
// Maybe change some other Controls
//..........
}
//..
//..
}
Now here I am updating multiple controls in the same function gradually as the code progresses step by step but it gets updated at last when execution is done.
I want to update the controls while execution as shown in the code.

This is a likely a duplicate of bits of other questions:
JavaFx: Update UI label asynchronously with messages while application different methods execution
JavaFx ProgressBar doesnt update
Platform.runLater and Task in JavaFX
Usage of JavaFX Platform.runLater and access to UI from a different thread
And perhaps some other questions.
As an overall approach, you define a Task, then within the execution body of the Task, you make use of Platform.runLater(), updateProgress() and other mechanisms to achieve what you need. See the related question for further explanations of these mechanisms.
final ImageView imageViewObj = new ImageView();
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
//Some Operations are carried out
//..
//Then I want to set Image in ImageView
// use Platform.runLater()
Platform.runLater(() -> imageViewObj.setImage(myImage));
// Some Code Here
//..
// Set Progress
updateProgress(0.1, 1);
// Some Code Here
//..
// Set Progress
updateProgress(0.2, 1);
int variable = 2;
final int immutable = variable;
// Maybe change some other Controls
// run whatever block that updates the controls within a Platform.runLater block.
Platform.runLater(() -> {
// execute the control update logic here...
// be careful of updating control state based upon mutable data in the task thread.
// instead only use immutable data within the runLater block (avoids race conditions).
});
variable++;
// some more logic related to the changing variable.
return null;
}
};
ProgressBar updProg = new ProgressBar();
updProg.progressProperty().bind(task.progressProperty());
Thread thread = new Thread(task, "my-important-stuff-thread");
thread.setDaemon(true);
thread.start();

Related

javafx textfield doesn't change

I have the following code:
#FXML
private void test(){
textField.setText("Pending...");
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
And what I tries to achieve is that while the doStuff() do his stuff in a textField in the GUI there should be written "Pending..." and as soon as it finish it should change to "OK" / "Error".
I want that the GUI is blocked while doStuff is running so the user has to wait and can't click something else.
But what happens is that as soon as I start test it does the doStuff() but only updates the textField with "OK"/"Error" but I never see "Pending...".
I have the feeling that I have somehow update the GUI, but I'm not sure how it should be done.
Update:
What I tried is to move the doStuff in another Thread:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
public void run(){
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
};
t.start();
t.join();
}
It would works if i would remove the t.join(); command, but then the UI wouldn't be blocked. So I'm at a loss right now.
Thanks
You must never run long running tasks on the JavaFX Application Thread. Doing so will prevent said thread from doing any GUI related things which results in a frozen UI. This makes your user(s) sad. However, your attempt at putting the long running task on a background task is flawed. You call Thread.join which will block the calling thread until the target thread dies; this is effectively the same thing as just running the task on the calling thread.
For a quick fix to your example, you could do the following:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
#Override public void run(){
boolean passed = doStuff();
Platform.runLater(() -> {
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
});
}
};
t.start();
}
That will create a thread, start it, and let it run in the background while letting the JavaFX Application Thread continue doing what it needs to. Inside the background thread you must update the TextField inside a Platform.runLater(Runnable) call. This is needed because you must never update a live scene graph from a thread other than the JavaFX Application Thread; doing so will lead to undefined behavior. Also, you should look into “implements Runnable” vs “extends Thread” in Java. It's better, or at least more idiomatic, to do:
Thread t = new Thread(() -> { /* background code */ });
You could also use a javafx.concurrent.Task which may make it easier to communicate back to the JavaFX Application Thread. One option would be:
#FXML
private void test(){
textField.setText("Pending...");
Task<Boolean> task = new Task<>() {
#Override protected Boolean call() throws Exception {
return doStuff();
}
};
task.setOnSucceeded(event -> textField.setText(task.getValue() ? "Ok" : "Error"));
new Thread(task).start();
}
You could also bind the TextField to the message property of the Task and call updateMessage("Pending...") inside the call method. You could even provide more detailed messages if and when possible.
That said, creating and starting Threads yourself is not ideal and you should look into thread pooling (using something like an ExecutorService). You might also want to look into javafx.concurrent.Service for "reusing" Tasks.
For more information about JavaFX concurrency see Concurrency in JavaFX and read the documentation of the classes in javafx.concurrent. For the basics of multi-threading in Java see Lesson: Concurrency from The Java™ Tutorials.

JavaFX: Not in Application Thread only when updating a Label?

for the last few days I have been experimenting with JavaFX, FXML, Tasks and Properties. I stumbled upon a strange behaviour and hope that you can help me to understand better what's going on.
I have a minimalistic GUI which looks like this: GUI
If I click on the Button a new Task is created and started. This Task increments a Double Property and the new Value is written onto the Label and set in the ProgressBar. The Code of the Task can be seen here:
public class TestTask extends Task<Void>{
private final DoubleProperty doubleValue = new SimpleDoubleProperty();
#Override
protected Void call() throws Exception {
for(double i = 0.1; i <= 1; i = i+0.1 ) {
doubleValue.set(i);
Thread.sleep(1000);
}
return null;
}
public DoubleProperty test() {
return doubleValue;
}
}
The code of the FXML Controller is the following:
public class FXMLDocumentController implements Initializable {
#FXML private Label label;
#FXML private Slider slider;
//Called when the Button is clicked
#FXML
private void handleButtonAction(ActionEvent event) {
TestTask task = new TestTask();
task.test().addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
//Works without problems
slider.setValue(newValue.doubleValue());
//Throws an exception
label.setText("Value" + newValue.doubleValue());
});
new Thread(task).start();
}
}
If I run this code the attempt to update the Label results in the following exception: java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4. The update of the Slider works fine and throws no exptions.
After reading the following questions on SO:
On which thread JavaFX change listeners are executed ?
Using threads to make database requests
I understand that the update of the Label fails because I'm trying to execute an UI Update within the Listener which is called from the TestTask Thread and not from the FXApplication Thread.
My question is now: Why does the Update of the Slider work und doesn't throw an exception? The update is carried out within the Listener and therefore from within the TestTask Thread. Shouldn't this attempt also throw a "Not on FX application thread" exception?
Thanks in advance for taking your time to help me.
Updating a property on one thread while you listen for changes to the property on another thread is just a race condition. Don't do it.
Not only does the JavaFX system assume updates to the UI occur on a single thread, it also assumes the same for properties, regardless of whether they are bound to UI elements. Don't rely on the JavaFX system to check for all race conditions and report and handle them correctly for you, because it is not designed to do that.
You need write your code in such a way as to prevent such conditions.

javafx show progressindicator while loading [duplicate]

This question already has an answer here:
JavaFX indeterminate progress bar while doing a process
(1 answer)
Closed 7 years ago.
This is the main window of my application:
I want to be able to update the window, so i wrote a method reset which ist triggered by pressing F5:
public void reset() {
final ProgressIndicator pi = new ProgressIndicator(-1.0f);
final HBox hb = new HBox(pi);
final VBox vb = new VBox(hb);
hb.setAlignment(Pos.CENTER);
vb.setAlignment(Pos.CENTER);
scene.setRoot(vb);
//this.show();
}
if i run this it looks sth like this:
However, if I uncomment the line //this.show(); this will be executed:
public void show() {
final VBox box = API_CALL_AND_BUILDING_BOX_MAGIC_WHICH_I_STRIPPED;
gp.getChildren().clear();
gp.getChildren().add(box);
scene.setRoot(gp);
stage.sizeToScene();
}
and I will never be able to see my ProgressIndicator because the application will just hang until the APIcall is finished and the new content is loaded.
I tried a bit with Threads and Platform.runLater() but I cant get it to work. My goal is to display the ProgressIndicator until the APIcall is finished, the box is build and the scene gets given gp as the new root.
I hope its somewhat understandable what my goal and my problems are :)
You need to create another JVM thread dedicated to fetch data from the remote source. The main thread of your application must never block with network operations: in the keyboard event handler you just repaint the main window with the progress indicator and then wait for another event, that can be:
background operation finished (successful, unsuccessful)
user interrupts (mouse/keyboard)
a timeout expires
Pseudocode with ListenableFuture by Guava
// Initialize in ctor use MoreExecutors
private final ListeningExecutorService service;
// Keep to call .cancel()
private ListenableFuture<APIData> currentRequest;
// Run in the GUI thread
public void onRefreshEvent() {
showProgressIndicator();
currentRequest = service.submit(/* Some Callable<APIData> */);
Futures.addCallback(currentRequest , this, this);
}
// From FutureCallback<T>
public void onFailure(Throwable t) {} // TODO
public void onSuccess(APIData d) {} // TODO
// From Executor
public void execute(Runnable cmd) {
Platform.runLater(cmd);
}
Don't forget to consider socket connect and read timeout, as well as pooling HTTP connection! Also consider if the background task should check the interrupted flag

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)
}

Wait for Runnables to finish

I have implemented a programm in javafx which dynamically generates an input mask and generates a Word-document after a button click on that mask.
I define in an db-table which input fields are available in this mask.
Now I'm adding support for custom procedures which are executed on specific states of my program (onFocusLost of an field,onChange,...)
Wich works perfect.
Now I'm stuck at the onGenerate execution.
When I render the mask, I hold a List of Runnables to store my actions which should be executed on generation (At the render time i know which action should be executed. At Generation time i would have to read all the data in again. So I thought I save the action with FunctionalInterface. And I need no inputParameter and no ReturnValue ... so i ended up with Runnable)
TextField tf = ...;
String s = ...;
actionsBeforeGenerate.add(() -> {
tf.setText(s);
});
So now, if I press the generate button, I do the following:
private void startGenerate(){
//main.getActionsBeforeGenerate() == List<Runnable>
main.getActionsBeforeGenerate().forEach(action -> action.run());
generateWordDocument();
}
The text is set to the node correctly, BUT after the document is generated.
How can I change the textField BEFORE my generationLogic starts?
If you need more information, don't hesitate to ask for it.
EDIT:
I think I've found the solution for my problem:
private void startGenerate(){
Task<Boolean> task = new Task<Boolean>() {
#Override
public Boolean call() throws Exception {
main.getActionsBeforeGenerate().forEach(action -> action.run());
....
generateWordDocument();
....
return Boolean.TRUE;
}
};
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
Now I have the new values textField values available in generateWordDocument()

Categories