JavaFX: How to make an looping thread without freezing application? - multithreading

How do I go about making a thread within JavaFX? I've looked around and no answers are clear/want I need, which is basicly the same as Java's Thread Runnable, which is not conpatible with JavaFx unless it's for a background task.
The basic start application class I have:
public class Main extends Application {
private Stage stage;
private AnchorPane rootLayout;
#Override
public void start(Stage stage) {
this.stage = stage;
this.stage.setTitle("Main");
setLayout();
}
private void setLayout() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("view/View.fxml"));
rootLayout = (AnchorPane) loader.load();
Scene scene = new Scene(rootLayout);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
I need a JavaFX Thread that'll keep changing the visibility of a button without freezing/pausing the application while it's running, how do I go about doing this?
Edit: I need this but for a JavaFX app:
public void run(){
while (true){
if (button.isVisible)
button.setVisibility(false);
else
button.setVisibility(true);
Thread.sleep(1000);
}
}

Related

JavaFX - Cancel Task doesn't work

In a JavaFX application, I have a method which takes a long time on large input. I'm opening a dialog when it is loading and I'd like the user to be able to cancel/close out the dialog and the task will quit. I created a task and added its cancellation in the cancel button handling. But the cancellation doesn't happen, the task doesn't stop executing.
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
// calling a function that does heavy calculations in another class
};
task.setOnSucceeded(e -> {
startButton.setDisable(false);
});
}
new Thread(task).start();
cancelButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
System.out.println("Button handled");
task.cancel();
}
);
Why isn't the task getting canceled when the button clicked?
You have to check on the cancel state (see Task's Javadoc). Have a look at this MCVE:
public class Example extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
new AnotherClass().doHeavyCalculations(this);
return null;
}
};
Button start = new Button("Start");
start.setOnMouseClicked(event -> new Thread(task).start());
Button cancel = new Button("Cancel");
cancel.setOnMouseClicked(event -> task.cancel());
primaryStage.setScene(new Scene(new HBox(start, cancel)));
primaryStage.show();
}
private class AnotherClass {
public void doHeavyCalculations(Task<Void> task) {
while (true) {
if (task.isCancelled()) {
System.out.println("Canceling...");
break;
} else {
System.out.println("Working...");
}
}
}
}
}
Note that…
You should use Task#updateMessage(String) rather than printing to System.out, here it's just for demonstration.
Directly injecting the Task object creates a cyclic dependency. However, you can use a proxy or something else that fits your situation.

java.lang.IllegalStateException: Not on FX application thread Calling Function

Something strange is happening.. Untill 10 minutes ago I had no problem with this code. But now I have a problem updating JUST my VBOX from an external thread.
These are my three classes:
Controller Class:
public class Controller implements Initializable{
#FXML
private VBox slaveVbox;
private ButtonBar newNode = new ButtonBar();
private Circle c= new Circle();
private Button b= new Button();
private Label lname = new Label();
private Label lIMEI = new Label();
private Label lroot = new Label();
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void create(String imei, String permission,boolean isOnline) throws IOException{
if(!alreadyExist(imei)){
newNode = new ButtonBar();
b = setButtonSpec(imei + "btnHavefun");
c = setCircleSpec(imei + "statuOnline", isOnline);
lname= setLNameSpec(imei + "name");
lIMEI = setLIMEISpec(imei + "Imei");
lroot = setLrootSpec(imei + "root", permission);
newNode.getButtons().addAll(lname,lIMEI,lroot,b,c);
slaveVbox.getChildren().addAll(newNode);
}
}
}
Main Class:
public class MainApp extends Application {
FXMLLoader loader2;
private Stage primaryStage;
private BorderPane rootLayout;
#Override
public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Thypheon Application");
initRootLayout();
Controller controller2 = initDesign();
Connection con = new Connection(controller2);
Thread t = new Thread(con);
t.start();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent e) {
Platform.exit();
System.exit(0);
}
});
}
public static void main(String[] args) {
launch(args);
}
public void initRootLayout(){
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Controller initDesign(){
try {
FXMLLoader loader2= new FXMLLoader(getClass().getResource("Design.fxml"));
AnchorPane anchor = (AnchorPane) loader2.load();
rootLayout.setCenter(anchor);
Controller controller = loader2.getController();
return controller;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public Stage getPrimaryStage(){
return primaryStage;
}
}
Connection THREAD:
public class Connection implements Runnable {
String result;
Controller controller;
public Connection(Controller controller) {
this.controller = controller;
}
#Override
public void run() {
try {
controller.create("jhgjhgjh", "dssf", true);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Debugging the Application Everything works perfectly untill I reach slaveVbox.getChildren().addAll(newNode); Here comes the exception..
After some attempt to solve the problem I figured out that if I create a ButtonBar and I insert it in the slaveVbox from Main (inside start()) it works fine.. So I ve tied to add controller2.create("FIRST", "FIRST", true); in my start() function like this:
#Override
public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Thypheon Application");
initRootLayout();
Controller controller2 = initDesign();
controller2.create("FIRST", "FIRST", true);
Connection con = new Connection(controller2);
Thread t = new Thread(con);
t.start();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent e) {
Platform.exit();
System.exit(0);
}
});
}
But obviously my application shows two ButtonBars... One created in the start() function and one created inside the Connection Thread.. How Can I avoid this?? Why I can't directly add item inside my VBox directly from my Connecton thread??
You cannot update the UI from a thread other than the FX Application Thread. See, for example, the "Threading" section in the Application documentation.
It's not at all clear why you are using a background thread at all here: there doesn't seem to be any long-running code in the method you are calling. In general, if you have long-running code to call, you can call it in a background thread and then update the UI by wrapping UI update in a Platform.runLater(...).
public class Connection implements Runnable {
String result;
Controller controller;
public Connection(Controller controller) {
this.controller = controller;
}
#Override
public void run() {
try {
// execute long-running code here...
// perform any updates to the UI on the FX Application Thread:
Platform.runLater(() -> {
// code that updates UI
});
// more long-running code can go here...
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

Binding Properties and using them during lengthy operations

In my JavaFX Application I want to disable a couple of Buttons during a refresh of the data from a database.
I am using the disableProperty of the Buttons I want to disable.
Here is the basic JavaFX Application, modefied to illustrate my point:
public class BindLengthy extends Application {
BooleanProperty disable = new SimpleBooleanProperty(false);
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.disableProperty().bind(disable);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
disable.set(true);
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
Logger.getLogger(BindLengthy.class.getName()).log(Level.SEVERE, null, ex);
}
btn.setText("Done");
}
});
//Do all the other stuff that needs to be done to launch the application
//Like adding btn to the scene and so on...
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
When executed, on the click the Button stays in the "fired" mode, waits for 5 Seconds and then changes text and disables. While I want the text to change later, I want to disableProperty Change to take effect immediately!
I tried putting the lengthy operation, represented by Thread.sleep(5000) into a task and start it on a new Thread(task), but then obviously the text is changes before the Thread awakens.
I can't put the btn.setText("Done")into the Threadas it wouldn't be executed on the JavaFX-Thread(which it needs to). So I tried joining the Thread, yet that gives the same result as not putting it into an extra Thread as well.
How can I force the diableProperty to register the new value before executing my long operation?
Use a Task and use its onSucceeded handler to update the UI:
public class BindLengthy extends Application {
BooleanProperty disable = new SimpleBooleanProperty(false);
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.disableProperty().bind(disable);
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
disable.set(true);
Task<String> task = new Task<String>() {
#Override
public String call() throws Exception {
Thread.sleep(5000);
return "Done" ;
}
});
task.setOnFailed(e ->
Logger.getLogger(BindLengthy.class.getName())
.log(Level.SEVERE, null, task.getException()));
task.setOnSucceeded(e -> {
btn.setText(task.getValue());
disable.set(false);
});
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
}
});
//Do all the other stuff that needs to be done to launch the application
//Like adding btn to the scene and so on...
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

Display a ProgressIndicator during an async loading of ListView items

I am trying to display a ProgressIndicator while performing an async background ListView item loading. The behaviour that I desire is:
Before start loading the ListView items, display a ProgressIndicator with a indeterminate progress;
Asynchronously start loading the ListView items;
After the ListView items loading was finished, hide the ProgressIndicator.
Here is a ssce of my unsuccessful attempt:
public class AsyncLoadingExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<String>();
final ObservableList<String> listItems = FXCollections.observableArrayList();
final ProgressIndicator loadingIndicator = new ProgressIndicator();
final Button button = new Button("Click me to start loading");
primaryStage.setTitle("Async Loading Example");
listView.setPrefSize(200, 250);
listView.setItems(listItems);
loadingIndicator.setVisible(false);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// I have hoped it whould start displaying the loading indicator (actually, at the end of this
// method execution (EventHandler.handle(ActionEvent))
loadingIndicator.setVisible(true);
// asynchronously loads the list view items
Platform.runLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000l); // just emulates some loading time
// populates the list view with dummy items
while (listItems.size() < 10) listItems.add("Item " + listItems.size());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
loadingIndicator.setVisible(false); // stop displaying the loading indicator
}
}
});
}
});
VBox root = VBoxBuilder.create()
.children(
StackPaneBuilder.create().children(listView, loadingIndicator).build(),
button
)
.build();
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
}
In this example, the ListView items are loaded asynchronously. However, the ProgressIndicator do not show up. Still in this example, if I omit all the Platform.runLater(...) code, the ProgressIndicator shows up, but, of course, the ListView items are not loaded.
Thus, how can I achieve the desired behaviour?
Crferreira's self answer is perfectly fine.
This answer just demonstrates an alternate implementation that does not require the use of any Platform.runLater calls and instead uses a JavaFX Task (as well as Java 8 lambda syntax).
import javafx.application.Application;
import javafx.collections.*;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.*;
public class AsyncLoadingExample extends Application {
private void loadItems(final ObservableList<String> listItems, final ProgressIndicator loadingIndicator) {
if (loadingIndicator.isVisible()) {
return;
}
// clears the list items and start displaying the loading indicator at the Application Thread
listItems.clear();
loadingIndicator.setVisible(true);
// loads the items at another thread, asynchronously
Task listLoader = new Task<List<String>>() {
{
setOnSucceeded(workerStateEvent -> {
listItems.setAll(getValue());
loadingIndicator.setVisible(false); // stop displaying the loading indicator
});
setOnFailed(workerStateEvent -> getException().printStackTrace());
}
#Override
protected List<String> call() throws Exception {
final List<String> loadedItems = new LinkedList<>();
Thread.sleep(2000l); // just emulates some loading time
// populates the list view with dummy items
while (loadedItems.size() < 10) {
loadedItems.add("Item " + loadedItems.size());
}
return loadedItems;
}
};
Thread loadingThread = new Thread(listLoader, "list-loader");
loadingThread.setDaemon(true);
loadingThread.start();
}
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<>();
final ObservableList<String> listItems = FXCollections.observableArrayList();
final ProgressIndicator loadingIndicator = new ProgressIndicator();
final Button button = new Button("Click me to start loading");
primaryStage.setTitle("Async Loading Example");
listView.setPrefSize(200, 250);
listView.setItems(listItems);
loadingIndicator.setVisible(false);
button.setOnAction(event -> loadItems(listItems, loadingIndicator));
VBox root = new VBox(
new StackPane(
listView,
loadingIndicator
),
button
);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
The problem is that, at the presented example, I am misusing the Platform.runLater(...) method, and consequently, the JavaFX Application Thread.
As mentioned at the Platform.runLater() method documentation, this method
Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future.
And, the JavaFX Application Thread is the thread from which the JavaFX scene graph can be accessed and modified by the developer code, visually reflecting the performed modifications.
Thus, when I start loading the ListView items from this thread, the UI becomes unresponsive (this is also stated here) until the loading is finished.
To solve the problem, the ListView items must be loaded at another thread and only the ListView update must be performed at Application Thread.
The above correction is presented in the following:
public class AsyncLoadingExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<String>();
final ObservableList<String> listItems = FXCollections.observableArrayList();
final ProgressIndicator loadingIndicator = new ProgressIndicator();
final Button button = new Button("Click me to start loading");
primaryStage.setTitle("Async Loading Example");
listView.setPrefSize(200, 250);
listView.setItems(listItems);
loadingIndicator.setVisible(false);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
final List<String> loadedItems = new LinkedList<String>();
// clears the list items and start displaying the loading indicator at the Application Thread
listItems.clear();
loadingIndicator.setVisible(true);
// loads the items at another thread, asynchronously
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000l); // just emulates some loading time
// populates the list view with dummy items
while (loadedItems.size() < 10) loadedItems.add("Item " + loadedItems.size());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// just updates the list view items at the
// Application Thread
Platform.runLater(new Runnable() {
#Override
public void run() {
listItems.addAll(loadedItems);
loadingIndicator.setVisible(false); // stop displaying the loading indicator
}
});
}
}
}).start();
}
});
VBox root = VBoxBuilder.create()
.children(
StackPaneBuilder.create().children(listView, loadingIndicator).build(),
button
)
.build();
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
}

JavaFX: How to disable a button for a specific amount of time?

I want to disable a button for a specific time in JavaFX application. Is there any option to do this? If not, is there any work around for this?
Below is my code in application. I tried Thread.sleep, but i know this is not the good way to stop the user from clicking on next button.
nextButton.setDisable(true);
final Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(delayTime),
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
nextButton.setDisable(false);
}
}));
animation.setCycleCount(1);
animation.play();
You could use the simple approach of a thread that provides the relevant GUI calls (through runLater() of course):
new Thread() {
public void run() {
Platform.runLater(new Runnable() {
public void run() {
myButton.setDisable(true);
}
}
try {
Thread.sleep(5000); //5 seconds, obviously replace with your chosen time
}
catch(InterruptedException ex) {
}
Platform.runLater(new Runnable() {
public void run() {
myButton.setDisable(false);
}
}
}
}.start();
It's perhaps not the neatest way of achieving it, but works safely.
You could also be using the Timeline:
final Button myButton = new Button("Wait for " + delayTime + " seconds.");
myButton.setDisable(true);
final Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(delayTime),
new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
myButton.setDisable(false);
}
}));
animation.setCycleCount(1);
animation.play();
The method to disable a JavaFX control is:
myButton.setDisable(true);
You can implement the time logic programmatically in any way you wish, either by polling a timer or by having this method invoked in response to some event.
If you have created this button instance through FXML in SceneBuilder, then you should assign the button an fx:id so that its reference is automatically injected into your controller object during the loading of the scene graph. This will make it easier for you to work with in your controller code.
If you have created this button programmatically, then you'll already have its reference available in your code.
Or you could use a Service and bind the running property to the disableProperty of the button do you want to disable.
public void start(Stage stage) throws Exception {
VBox vbox = new VBox(10.0);
vbox.setAlignment(Pos.CENTER);
final Button button = new Button("Your Button Name");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Service<Void> service = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
Thread.sleep(5000);//Waiting time
return null;
}
};
}
};
button.disableProperty().bind(service.runningProperty());
service.start();
}
});
vbox.getChildren().addAll(button);
Scene scene = new Scene(vbox, 300, 300);
stage.setScene(scene);
stage.show();
}
But the Timeline solution given by Uluk Biy, looks more elegant.

Resources