javafx background Task is not running more than once - javafx-2

The following is my Task initialization
final Task<Void> vt=voiceTask();
Button btn = new Button();
btn.setText("Say");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
new Thread(vt).start();
}
});
And here is the task coding
public Task<Void> voiceTask() {
return new Task<Void>(){
#Override
protected Void call() throws Exception {
HelloWorld hw=new HelloWorld();// HelloWorld is simple .java class getting voice through sphinx
updateMessage(hw.Hello());
return null;
}
};
}
Now on clicking the btn Button for the first time,the task functions normally, but on clicking for subsequent times the task is not called.
I want task to be called on every click.
Please advice me how to modify my code to do so...

See the JavaDocs.
As with FutureTask, a Task is a one-shot class and cannot be reused.
You need to create a new Task each time the button is pressed.
final Button btn = new Button();
btn.setText("Say");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
final Task<Void> vt=voiceTask();
vt.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
btn.setDisable(false);
}
});
btn.setDisable(true);
new Thread(vt).start();
}
});

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.

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.

Stage is hidden when dialog is shown

I have this code which displays confirmation dialog to exit application.
public class DialogPanels
{
public void initClosemainAppDialog(final Stage primaryStage)
{
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>()
{
#Override
public void handle(WindowEvent event)
{
event.consume(); // Do nothing on close request
// Dialog Stage init
final Stage dialog = new Stage();
dialog.initModality(Modality.APPLICATION_MODAL);
// Frage - Label
Label label = new Label("Exit from the program");
// Button "Yes"
Button okBtn = new Button("Yes");
okBtn.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
//primaryStage.close();
//dialog.close();
//Platform.exit();
System.exit(0);
}
});
// Button "No"
Button cancelBtn = new Button("No");
cancelBtn.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
primaryStage.show();
dialog.close();
}
});
// Layout for the Button
HBox hbox = new HBox();
hbox.setSpacing(10);
hbox.setAlignment(Pos.CENTER);
hbox.getChildren().add(okBtn);
hbox.getChildren().add(cancelBtn);
// Layout for the Label and hBox
VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
vbox.setSpacing(10);
vbox.getChildren().add(label);
vbox.getChildren().add(hbox);
// Stage
Scene scene = new Scene(vbox);
dialog.setScene(scene);
dialog.show();
}
});
}
}
The problem is that when close the main application the dialog box is displayed and the main stage is hidden. I want to display the dialog box in front of the main stage. Can you help me to correct this?
UPDATE
I tested this code, it's working but when the dialog is displayed the mainstage is not responsible(frozen). How I an make the mainstage responsible when I display dialog?
Consume the closing event and set the owner of the stage if you do not want to see another window when the windows are minimized:
#Override
public void handle(WindowEvent event)
{
event.consume(); // Do nothing on close request
// Dialog Stage init
final Stage dialog = new Stage();
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.initOwner(primaryStage);
// other stuff
}
});
You need to set the proper relationships between primaryStage and dialog stage. Here's a hint to get you going:
...
dialog.initOwner(primaryStage);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.showAndWait();
You can find more information in Oracle's JavaFX 2 JavaDocs.
More example code (edit)
I'm using setOnHiding(..) instead of setOnCloseRequest(..):
stage.setOnHiding(new AskUserIfHeReallyWantsToQuitWindowHandler(stage));
I extracted your code into a seperate event handler class and fixed the issues I mentioned (sorry, I am little short on time right now):
public class AskUserIfHeReallyWantsToQuitWindowHandler implements EventHandler<WindowEvent> {
private final Stage primaryStage;
public AskUserIfHeReallyWantsToQuitWindowHandler(final Stage primaryStage) {
Objects.requireNonNull(primaryStage);
this.primaryStage = primaryStage;
}
#Override
public void handle(final WindowEvent event) {
event.consume();
final Stage dialog = new Stage();
final Button okBtn = new Button("Yes");
okBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent event) {
dialog.close();
primaryStage.close();
}
});
// Button "No"
final Button cancelBtn = new Button("No");
cancelBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent event) {
dialog.close();
Platform.runLater(new Runnable() {
#Override
public void run() {
primaryStage.show();
}
});
}
});
// Layout for the Button
final HBox hbox = new HBox();
hbox.setSpacing(10);
hbox.setAlignment(Pos.CENTER);
hbox.getChildren().add(okBtn);
hbox.getChildren().add(cancelBtn);
// Layout for the Label and hBox
final VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
vbox.setSpacing(10);
vbox.getChildren().add(new Label("Do your really want to exit?"));
vbox.getChildren().add(hbox);
// Stage
final Scene scene = new Scene(vbox);
dialog.setScene(scene);
dialog.initOwner(primaryStage);
dialog.initModality(Modality.NONE);
dialog.showAndWait();
}
}

Resources