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
Related
I am writing an application on javaFX and as a part of my view(in MVVM architecture) I am using an FXML Label for the status of my application to present for the client(for example: if a file was uploaded successfully or not).
I want to clean this status label a few seconds after an update was made.
I used this code to run a thread to do so but the problem occurs when more than one update was made before the previous finished its job.
I thought of canceling any previous threads in the thread-pool before executing a new thread but I didn't find a way to do so.
public class MainWindowController implements Observer {
ViewModel vm;
ScheduledExecutorService scheduledExecutorService;
Stage stage;
#FXML
Label appStatus;
public void loadProperties(){
FileChooser fc = new FileChooser();
fc.setTitle("Load Project Properties");
fc.setInitialDirectory(new File("./resources"));
FileChooser.ExtensionFilter extensionFilter = new FileChooser.ExtensionFilter(
"XML Files (*.xml)", "*.xml");
fc.getExtensionFilters().add(extensionFilter);
File chosenFile = fc.showOpenDialog(stage);
//CONTINUE HERE
if(chosenFile==null){
appStatus.setTextFill(Color.RED);
vm.appStat.setValue("Failed to load resource");
}
else{
vm.setAppProperties(chosenFile.getAbsolutePath());
}
cleanStatusBox();
}
public void cleanStatusBox(){
scheduledExecutorService.schedule(new Runnable() {
#Override
public void run() {
Platform.runLater(()->vm.appStat.setValue(""));
}
},10000, TimeUnit.MILLISECONDS);
}
public void setViewModel(ViewModel vm) {
this.vm = vm;
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
appStatus.textProperty().bind(vm.appStat);
}
}
A ScheduledExecutorService (and using background threads in general) is far too heavy-handed an approach for this.
Instead, consider using a PauseTransition, which once started will execute its onFinished handler on the JavaFX Application Thread after the specified time. The playFromStart() method "restarts" the pause, enabling multiple updates without conflict.
public class MainWindowController implements Observer {
ViewModel vm;
PauseTransition clearLabelPause;
Stage stage;
#FXML
Label appStatus;
public void loadProperties(){
FileChooser fc = new FileChooser();
fc.setTitle("Load Project Properties");
fc.setInitialDirectory(new File("./resources"));
FileChooser.ExtensionFilter extensionFilter = new FileChooser.ExtensionFilter(
"XML Files (*.xml)", "*.xml");
fc.getExtensionFilters().add(extensionFilter);
File chosenFile = fc.showOpenDialog(stage);
//CONTINUE HERE
if(chosenFile==null){
appStatus.setTextFill(Color.RED);
vm.appStat.setValue("Failed to load resource");
}
else{
vm.setAppProperties(chosenFile.getAbsolutePath());
}
clearLabelPause.playFromStart();
}
public void setViewModel(ViewModel vm) {
this.vm = vm;
clearLabelPause = new PauseTransition(Duration.seconds(10));
clearLabelPause.setOnFinished(e -> vm.appStat.setValue(""));
appStatus.textProperty().bind(vm.appStat);
}
}
The schedule method returns a ScheduledFuture which has a cancel method. You are currently discarding that.
Attempts to cancel execution of this task. This attempt will fail if
the task has already completed, has already been cancelled, or could
not be cancelled for some other reason. If successful, and this task
has not started when cancel is called, this task should never run. If
the task has already started, then the mayInterruptIfRunning parameter
determines whether the thread executing this task should be
interrupted in an attempt to stop the task.
After this method returns,
subsequent calls to isDone() will always return true. Subsequent calls
to isCancelled() will always return true if this method returned true.
In your case, if the task hasn't started then it will be removed from the queue of tasks, and it will be as if you never scheduled it.
If the task is currently running then you will not be able to cancel it, since you do not check the thread's interrupt flag. Whether you pass true or false to cancel() will not make a difference.
All your task does is push a task onto another thread, so the scheduled executor task is likely to be extremely quick. Because of that, it doesn't really matter that you don't check the interrupt flag. For longer running tasks which involve more steps - do X, then Y, then Z - then it would be important to check the flag.
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.
Im stuck programing a Tetris game trying to learn Java and JavaFX.
When I call the method the first time from method start it runs well, when I call it from the thread it only runs partially, and dont add a new node to the pane.
Here is part of the code, you can view it entirely in
#Override
public void start(Stage stage) {
...
stage.show();
newPieces();
}
public void newPieces() {
Pieces pieces = new Pieces();
piece = pieces.createPiece();
nextPiece = pieces.createPiece();
boolean add1 = vbox.getChildren().add(nextPiece);
boolean add2 = pane.getChildren().add(piece);
translateDown(piece);
}
public void translateDown(Group piece) {
scheduler.scheduleWithFixedDelay(new Runnable() {
#Override
public void run() {
boolean translate = piece.getTransforms().add(new Translate(0, 25));
...
...
newPieces();
scheduler.shutdown();
...
...
}, 0, 1, TimeUnit.SECONDS);
The method newPieces creates a Group of Rectangles.
Thanks!
Instead of using a executor to schedule the updates I recommend using a Timeline which executes a frame's event handler on the application thread. Running the updates on a application thread is important since modifying properies of nodes on a different thread is problematic:
Timeline timeline = new Timeline();
timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1), evt -> {
updateBoard(); // do things like moving a piece...
if (checkLoss()) {
// stop updates when game is lost
timeline.stop();
}
}));
// repeat indefinitely
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
You could also use timeline.pause() to pause the game and set the rate property to speed up the game...
If you want to access the javafx gui components you have to run the code on the javafx thread. The extra thread you start is not allowed to access the javafx elements. The exception thrown is probably uncatched.
Try Platform.runLater(Runnable runnable) with the code modifing the gui. This runs the specified runnable on the javafx thread at some time in the future.
Basically, I'm creating a program that syncs HUE lights, and I'm having trouble incorporating the Listener that detects when the light bridge has been connected with my JavaFX GUI; I want it to switch a Label from "Not Connected" to "Connected" whenever the listener detects that it has connected.
Here's some pseudocode of how the program is structured.
public class MainClass extends Application {
boolean connected;
Label label;
public static void main(){
launch(args); //Neccesary to start JavaFX
}
public static void start(){
ConnectToHueLights(); //Takes abt 30s to connect to bridge
Label label = “Searching for connection”; //Message while connecting
Window.addLabel(); //Adds label to hue lights
Window.show(); //Makes window visible
}
private HueLightsListener(){
//Once connected, can do whatever inside of a void method inside of this Listener
private void onConnectionResponds(){
label = “Connected”
connected = true;
}
}
public void ConnectToHueLights(){
create new Listener();
}
Basically, the label doesn't change whenever the listener is active, and I'm not sure how to do that.
Thanks!
Use a suitable Worker to establish the connection to the bridge. Choose Task for a single unit of work; choose Service to manage multiple tasks. Use the task's updateMessage() method to notify the worker's message property listeners. You can pass a reference to the update method to your HueLightsListener, as shown here.
HueLightsListener listener = new HueLightsListener(this::updateMessage);
Your implementation of onConnectionResponds() can then tell the reference to accept() messages as needed.
public void onConnectionResponds() {
updater.accept("Connected");
…
}
As an aside, your implementation of call(), which runs in the background, can periodically poll the connection, while checking isCancelled(), and then send more commands once connected.
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)
}