I use javafx, I have a TextField and a Button, when the button is pressed, it saves what is written in the TextField in a String. What I want to create is a method to mark a pause, while waiting for the Button to get pressed.
I have a class named pause.java, where I tried to put a obj.wait(); and a notifyAll(); in the event where the button is pressed, but the window isn't accessible during this time, I can't press the button or enter anything in the TextField.
So what I found was to put the obj.wait(); in a task, then I don't know why but it directly breaks out of the wait.
Here is my pause.java
package net.jpajavafx;
import java.util.logging.*;
import javafx.concurrent.Task;
public class pause {
Logger logger = Logger.getLogger(pause.class.getName());
MainController obj = new MainController();
public void waitinput() {
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
synchronized (obj) {
try {
String write = "Waiting for input...";
logger.log(Level.INFO, write);
obj.wait();
logger.log(Level.INFO, "Done");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
};
new Thread(sleeper).start();
}
}
How do I have to modify it to make it wait, while still having access to the GUI?
Here's my code simplified for the problem:
AlbumManager.java, where my main is.
package net.jpajavafx;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.*;
import javafx.application.*;
import javafx.fxml.FXMLLoader;
public class AlbumManager extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
primaryStage.setTitle("Album Manager");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java:
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
notifyAll();
}
public void startprogram() {
int etat = 0;
int run = 1;
while (run == 1) {
textarea.setText("1: launch method");
pause.waitinput(); // here I want to wait for an input
etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
}
It's really not clear what you're trying to achieve here that needs a separate thread: all the separate thread seems to try to do is wait until the button is pressed, and then execute some code. That functionality is already provided by the event management system in JavaFX (and the same is true for any UI toolkit): just execute the code in the event handler.
(As an aside, your use of wait() is incorrect, and if you fix that, the thread will never wake up because you are not calling notifyAll() on the same object on which you are calling wait().)
You can achieve what you seem to be trying to do simply with
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
processInput();
}
public void processInput() {
int etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
You have to start another thread using a Runnable, so your UI thread does not get blocked while the save-operation completes.
You can do this by placing a listener on the button that will start the save-operation on a new thread when the button is clicked.
The code for adding a listener to a button that starts a new thread would look something like this:
//Creating the mouse event handler
EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
MainController controller = new MainController();
controller.start();
}
};
//Registering the event filter
button.addEventFilter(MouseEvent.MOUSE_CLICKED, eventHandler);
The code you posted doesn't really do anything. Your call to waitinput() only logs and calls wait(). wait() is not what you want, since this operation is intended for putting a thread on hold until it is notified, not for executing a task in a seperate thread. Remove the obj.wait(), and add a listener that calls your logging method when the button is clicked. Also, get rid of the while-loop. The EventHandler will take care of events in the background.
Related
I am trying to create my very first application in JavaFX and I have a problem with Button that calls a method (for example to open another window) - I always have to click it twice in order to trigger action.
Here's my code from the Controller:
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class ControllerSignIn {
#FXML
private Button forgot;
#FXML
private Button back;
#FXML
private Button signin;
public void forgetPasswordClicked() {
forgot.setOnAction(e -> ForgotPassword.setUpWindow()); //works on 2nd click
}
public void backClicked() {
back.setOnAction(e -> ForgotPassword.closeWindow()); //works on 2nd click
}
public void signInClicked() {
System.out.println("Sign In CLICKED"); //works on first click
}
}
My methods are implemented here:
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
public class ForgotPassword {
static Stage window;
static Scene scene;
static Parent root;
private static void loadFXML() {
try {
root = FXMLLoader.load(ForgotPassword.class.getResource("ForgotPassword.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void setUpWindow() {
loadFXML();
scene = new Scene(root);
scene.getStylesheets().add("signin/SignIn.css");
window = new Stage();
window.initModality(Modality.APPLICATION_MODAL);
window.setTitle("Forgot Password?");
window.setScene(scene);
window.showAndWait();
}
public static void closeWindow() {
window.close();
}
}
Most likely you have the following in your FXML:
<Button fx:id="forgot" onAction="#forgetPasswordClicked" />
This makes your button forgot call your method forgetPasswordClicked(). But instead of defining your logic to be executed when your button is clicked, the first time you say: "When this button is clicked, place an action event on my button which will call setUpWindow()"
forgot.setOnAction(e -> ForgotPassword.setUpWindow());
Therefore, your first click "sets up" the logic of your button. The second click, actually executes it. To solve this, either immediately use your logic as such:
public void forgetPasswordClicked() {
ForgotPassword.setUpWindow();
}
or don't define the method to be called in your fxml, and move the initialization of your button (setting the action listener) to your initialization as following:
public class ControllerSignIn implements Initializable {
#FXML
private Button forgot;
#FXML
private Button back;
#Override
public void initialize(URL location, ResourceBundle resources) {
forgot.setOnAction(e -> ForgotPassword.setUpWindow());
back.setOnAction(e -> ForgotPassword.closeWindow());
}
}
This is also why your signInClicked() method works from the first click, because it actually executes the logic instead of setting up the handler first.
My goal here is to have some animation on a node (such as a fade transition) that serves as a temporary notice that something is happening. I want the animation completely gone, like it never happened when that something has ended.
The code snipped below is an example of the problem I'm having. In the current state, when the button is hit to stop the process the button just stays at it's current opacity. If the commented line is uncommented, the button no longer stays at it's current opacity but updates to look correct. My problem then is that when the button is hit again, the CSS opacity for the default stylesheet (Modena.css for JavaFX 8) is no longer taking effect.
Is there something I'm doing wrong, or is there a better way altogether?
package gui.control.custom;
import javafx.animation.Animation;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Test extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Stage stage = new Stage();
HBox box = new HBox();
streamButton = new Button("Start");
streamButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (started) {
stopProcess();
} else {
startProcess();
}
}
});
box.getChildren().add(streamButton);
stage.setScene(new Scene(box));
stage.show();
}
FadeTransition ft;
Button streamButton;
boolean started = false;
private void startProcess() {
streamButton.setDisable(true);
new Thread() {
#Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
}
Platform.runLater(() -> {
started = true;
streamButton.setText("Stop");
streamButton.setDisable(false);
startButtonAnim();
});
}
}.start();
}
private void stopProcess() {
streamButton.setText("Start");
stopButtonAnim();
started = false;
}
private void startButtonAnim() {
ft = new FadeTransition(Duration.millis(500), streamButton);
ft.setFromValue(1.0);
ft.setToValue(0.3);
ft.setCycleCount(Animation.INDEFINITE);
ft.setAutoReverse(true);
ft.play();
}
private void stopButtonAnim() {
ft.stop();
//streamButton.setOpacity(1);
}
public static void main(String[] args) {
launch();
}
}
I think the best solution is to use jumpTo(Duration duration) right before you stop the Animation. Setting the duration to Duration.ZERO.
Circle circle2 = new Circle(250, 120, 80);
circle2.setFill(Color.RED);
circle2.setStroke(Color.BLACK);
FadeTransition fade = new FadeTransition();
fade.setDuration(Duration.millis(5000));
fade.setFromValue(10);
fade.setToValue(0.1);
fade.setCycleCount(1000);
fade.setAutoReverse(true);
fade.setNode(circle2);
fade.play();
Button btnStop = new Button("Stop");
btnStop.setOnAction((event) -> {
fade.jumpTo(Duration.ZERO);
fade.stop();
});
Another idea:
I have found this method in javadoc: getCurrentRate(),
which should give you negative result on reversing, so the code would look like this:
private void stopButtonAnim() {
while(ft.getCurrentRate>=0); //waiting till animation goes (skips if already reversing)
while(ft.getCurrentRate<=0); //and till reverse
ft.stop(); //then stop
streamButton.setOpacity(1); //make it 100% ;)
}
Maybe you have to add Thread.sleep(int) to while cycle
I would try this insetad of simply stop(); this line
setOnFinished(e->tryToStop());
And create this method as:
public void tryToStop(){
if(!started)
fm.stop();
}
stopProcess() method changes the started variable, so it will stop in this two cases:
if it is finished
AND
if it is reqested to stop
Not tested, just an idea
I have made a GUI using JavaFX, and there are three radio buttons and once the user clicks submit and another thread is created and depending on what radiobutton was checked, the thread runs the required output and outputs the result to the console.
But while the thread is running (it takes around good 30 seconds for one process to complete) , I am able to check on any radiobutton. To it creates another thread and outputs long with the other ongoing thread. So my output box is just a jumble-wumble! I was looking at asynchronous task but I am not sure if that is something related to it.
Here is what I need: If a task is running, and I click on the submit button while it is running, wait for the previous task to END and THEN do the task.
Here is a psuedo code of my code
class TestMain {
//main
public void main(String ... args) {
launch(args);
}
/*declaring a new textfield with name m_status update here*/
/*once submit button is clicked*/{
//create a new thread
//to run
}
}
class ThreadBlahBlah implements Runnable {
if(/*first checkbox was selected*/){
//do these fancy stuff
Platform.runLater(new Runnable() {
#Override
public void run() {
TestMain.m_status_update.setText("Test Completed!");
}
});
}else if(/*second checkbox was selected*/){
//do these other fancy stuff
Platform.runLater(new Runnable() {
#Override
public void run() {
TestMain.m_status_update.setText("Test Completed!");
}
});
}
}
Please do not recommend me to disable radio buttons while the task is running cause I want to queue my tasks like a linked list.
Use a single-threaded executor to run your tasks:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class QueuedTaskExample extends Application {
private AtomicInteger taskCount = new AtomicInteger(0);
private ExecutorService exec = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r);
t.setDaemon(true); // allows app to exit if tasks are running
return t ;
});
// Use the following if you want the tasks to run concurrently, instead of consecutively:
// private ExecutorService exec = Executors.newCachedThreadPool(r -> {
// Thread t = new Thread(r);
// t.setDaemon(true);
// return t ;
// });
#Override
public void start(Stage primaryStage) {
// Just keep track of number of tasks pending/running for a status label:
IntegerProperty pendingTasks = new SimpleIntegerProperty(0);
Button startButton = new Button("Start");
TextArea textArea = new TextArea();
textArea.setEditable(true);
startButton.setOnAction(event -> {
Task<Void> task = createTask();
// add text to text area if task's message changes:
task.messageProperty().addListener((obs, oldMessage, newMessage) -> {
textArea.appendText(newMessage);
textArea.appendText("\n");
});
// for maintaining status label:
pendingTasks.set(pendingTasks.get()+1);
task.setOnSucceeded(taskEvent -> pendingTasks.set(pendingTasks.get()-1));
// run task in single-thread executor (will queue if another task is running):
exec.submit(task);
});
// layout etc
HBox controls = new HBox(startButton);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(10));
Label statusLabel = new Label();
statusLabel.textProperty().bind(Bindings.format("Pending/running tasks: %s", pendingTasks));
BorderPane root = new BorderPane(textArea, statusLabel, null, controls, null);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() {
exec.shutdownNow();
}
// Trivial task that counts slowly to 5, updating its message as it goes:
private Task<Void> createTask() {
final int taskNumber = taskCount.incrementAndGet();
return new Task<Void>() {
#Override
public Void call() throws Exception {
for (int count=1; count<=5; count++) {
Thread.sleep(1000);
updateMessage("Task "+taskNumber+": Count "+count);
}
return null ;
}
};
}
public static void main(String[] args) {
launch(args);
}
}
When the user clicks on a radio button, first disable all radio buttons so the user will not be able to click on other radio buttons while your task is running.
When you finished with the background job, re-enable all radio buttons so the user can choose another task.
See Node.setDisabled() (RadioButton extends Node).
If you do need to queue tasks, your background thread should maintain a a task list, and when the user clicks, add the task to the list, which the background thread should consume (start another task if the current one is completed and there are more).
For advanced threaded execution see Executors and ExecutorService.
I am developing an application with several TextField objects that need to be updated to reflect changes in associated back-end properties. The TextFields are not editable, only the back-end may change their content.
As I understand, the correct way about this is to run the heavy computation on a separate thread so as not to block the UI. I did this using javafx.concurrent.Task and communicated a single value back to the JavaFX thread using updateMessage(), which worked well. However, I need more than one value to be updated as the back-end does its crunching.
Since the back-end values are stored as JavaFX properties, I tried simply binding them to the textProperty of each GUI element and let the bindings do the work. This doesn't work, however; after running for a few moments, the TextFields stop updating even though the back-end task is still running. No exceptions are raised.
I also tried using Platform.runLater() to actively update the TextFields rather than binding. The issue here is that the runLater() tasks are scheduled faster than the platform can run them, and so the GUI becomes sluggish and needs to time to "catch up" even after the back-end task is finished.
I found a few questions on here:
Logger entries translated to the UI stops being updated with time
Multithreading in JavaFX hangs the UI
but my issue persists.
In summary: I have a back-end making changes to properties, and I want those changes to appear on the GUI. The back-end is a genetic algorithm, so its operation is broken down into discrete generations. What I would like is for the TextFields to refresh at least once in between generations, even if this delays the next generation. It is more important that the GUI responds well than that the GA runs fast.
I can post a few code examples if I haven't made the issue clear.
UPDATE
I managed to do it following James_D's suggestion. To solve the issue of the back-end having to wait for the console to print, I implemented a buffered console of sorts. It stores the strings to print in a StringBuffer and actually appends them to the TextArea when a flush() method is called. I used an AtomicBoolean to prevent the next generation from happening until the flush is complete, as it is done by a Platform.runLater() runnable. Also note that this solution is incredibly slow.
Not sure if I completely understand, but I think this may help.
Using Platform.runLater(...) is an appropriate approach for this.
The trick to avoiding flooding the FX Application Thread is to use an Atomic variable to store the value you're interested in. In the Platform.runLater method, retrieve it and set it to a sentinel value. From your background thread, update the Atomic variable, but only issue a new Platform.runLater if it's been set back to its sentinel value.
I figured this out by looking at the source code for Task. Have a look at how the updateMessage method (line 1131 at the time of writing) is implemented.
Here's an example which uses the same technique. This just has a (busy) background thread which counts as fast as it can, updating an IntegerProperty. An observer watches that property and updates an AtomicInteger with the new value. If the current value of the AtomicInteger is -1, it schedules a Platform.runLater.
In the Platform.runLater, I retrieve the value of the AtomicInteger and use it to update a Label, setting the value back to -1 in the process. This signals that I am ready for another UI update.
import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class ConcurrentModel extends Application {
#Override
public void start(Stage primaryStage) {
final AtomicInteger count = new AtomicInteger(-1);
final AnchorPane root = new AnchorPane();
final Label label = new Label();
final Model model = new Model();
final NumberFormat formatter = NumberFormat.getIntegerInstance();
formatter.setGroupingUsed(true);
model.intProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(final ObservableValue<? extends Number> observable,
final Number oldValue, final Number newValue) {
if (count.getAndSet(newValue.intValue()) == -1) {
Platform.runLater(new Runnable() {
#Override
public void run() {
long value = count.getAndSet(-1);
label.setText(formatter.format(value));
}
});
}
}
});
final Button startButton = new Button("Start");
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
model.start();
}
});
AnchorPane.setTopAnchor(label, 10.0);
AnchorPane.setLeftAnchor(label, 10.0);
AnchorPane.setBottomAnchor(startButton, 10.0);
AnchorPane.setLeftAnchor(startButton, 10.0);
root.getChildren().addAll(label, startButton);
Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public class Model extends Thread {
private IntegerProperty intProperty;
public Model() {
intProperty = new SimpleIntegerProperty(this, "int", 0);
setDaemon(true);
}
public int getInt() {
return intProperty.get();
}
public IntegerProperty intProperty() {
return intProperty;
}
#Override
public void run() {
while (true) {
intProperty.set(intProperty.get() + 1);
}
}
}
}
If you really want to "drive" the back end from the UI: that is throttle the speed of the backend implementation so you see all updates, consider using an AnimationTimer. An AnimationTimer has a handle(...) which is called once per frame render. So you could block the back-end implementation (for example by using a blocking queue) and release it once per invocation of the handle method. The handle(...) method is invoked on the FX Application Thread.
The handle(...) method takes a parameter which is a timestamp (in nanoseconds), so you can use that to slow the updates further, if once per frame is too fast.
For example:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1);
TextArea console = new TextArea();
Button startButton = new Button("Start");
startButton.setOnAction(event -> {
MessageProducer producer = new MessageProducer(messageQueue);
Thread t = new Thread(producer);
t.setDaemon(true);
t.start();
});
final LongProperty lastUpdate = new SimpleLongProperty();
final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
if (now - lastUpdate.get() > minUpdateInterval) {
final String message = messageQueue.poll();
if (message != null) {
console.appendText("\n" + message);
}
lastUpdate.set(now);
}
}
};
timer.start();
HBox controls = new HBox(5, startButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(console, null, null, controls, null);
Scene scene = new Scene(root,600,400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class MessageProducer implements Runnable {
private final BlockingQueue<String> messageQueue ;
public MessageProducer(BlockingQueue<String> messageQueue) {
this.messageQueue = messageQueue ;
}
#Override
public void run() {
long messageCount = 0 ;
try {
while (true) {
final String message = "Message " + (++messageCount);
messageQueue.put(message);
}
} catch (InterruptedException exc) {
System.out.println("Message producer interrupted: exiting.");
}
}
}
public static void main(String[] args) {
launch(args);
}
}
The best way to performing this is by usage of Task in JavaFx. This is be by far the best technique I've come across to update UI Controls in JavaFx.
Task task = new Task<Void>() {
#Override public Void call() {
static final int max = 1000000;
for (int i=1; i<=max; i++) {
updateProgress(i, max);
}
return null;
}
};
ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(task.progressProperty());
new Thread(task).start();
First of all I'm new at JavaFX, so sorry if this question is stupid. How can I get an return Object from a Task?
Heres my Problem:
I want to get a List of Objects from a Mock. The Mock has a delay from 1 up to 5 seconds. But I dont want, that my GUI freeze in this time.
In Java.Swing it was easy with an Thread, but JavaFX has, as far as I know, Tasks.
I've read much tutorials, but everywhere they return a text property. So here is my question: how can I set the value of an Object with the result of the calculation from a Task/Thread (in my case a List)
Thank you.
Ray,
You are right in that the examples seem to gloss over getting back results from tasks. There are two ways that you can get back results that I know of:
Through the getValue() method of the Task class (this is the way I have done it)
Through the get() method of the parent FutureTask class (I haven't used this, but in principle it should work).
With the first getValue() aproach you need to make sure the task sets the value through the updateValue(...) method in the call method of the task. Then put a listener on the WorkerStateEvent
myTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#SuppressWarnings("unchecked")
#Override
public void handle(WorkerStateEvent event) {
ReturnType rt = (ReturnType) event.getSource().getValue()
// ... other stuff to do here ...
}
});
The first approach is a little verbose but it works and allows for some more complicated operations after the task has finished.
The second approach is a little simpler and straightforward but doesn't give you as much control over what to do when the task finishes. With the get() method of FutureTask, the code should block until the Task returns with the value. So using it should be as simple as:
//
// Start the task in a thread (using whatever approach you like)
//before calling the get() method.
//
ReturnType rt = myTask.get();
I have used Future objects with other code, but I have not used FutureTask with the FX api, so I can not tell you if there are hidden gotchas in it.
Good luck,
chooks
The Task is a generic type. That means that if you apply a type to a Task like Task<Integer> the Task class will have functions that returns you an Integer. One of this functions is the valueProperty(), that can be bind to other scene elements. Being shown in a Label, or whatever. I recommend you to read the javafx binding tutorial, to get a better comprehension.
Here is a sample of Task using or their properties.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestingTasks extends Application{
public static void main(String[] args) {launch(args);}
#Override
public void start(Stage stage) throws Exception {
VBox vbox = new VBox(10);
vbox.setAlignment(Pos.TOP_CENTER);
ListView<String> list = new ListView<>();
HBox hbox = new HBox(10);
hbox.setAlignment(Pos.CENTER_LEFT);
Label labelMessage = new Label();
hbox.getChildren().addAll(new Label("Message: "), labelMessage);
ProgressBar progress = new ProgressBar(-1);
progress.setVisible(false);
Button button = new Button("Executing Task");
button.setOnAction(event(button, list, progress, labelMessage));
vbox.getChildren().addAll(list, hbox, button, progress);
Scene scene = new Scene(vbox, 400, 300);
stage.setScene(scene);
stage.show();
}
private EventHandler<ActionEvent> event(final Button button, final ListView<String> list, final ProgressBar progress, final Label labelMessage) {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Task<ObservableList<String>> task = generateTask();
list.itemsProperty().bind(task.valueProperty());
progress.visibleProperty().bind(task.runningProperty());
labelMessage.textProperty().bind(task.messageProperty());
button.disableProperty().bind(task.runningProperty());
task.runningProperty().addListener(listenerRunningTask());
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
}
};
}
private Task<ObservableList<String>> generateTask() {
return new Task<ObservableList<String>>() {
#Override
protected ObservableList<String> call() throws Exception {
updateMessage("Waiting...");
Thread.sleep(5000);
updateMessage("Waking up");
return FXCollections.observableArrayList("One", "Two", "Three");
}
};
}
private ChangeListener<? super Boolean> listenerRunningTask() {
return new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(oldValue && !newValue){
//TODO when finish
}
}
};
}
}
So basically, you can return a variable in the Task, or wait the Task ends and execute something, create your own bindings...
If you want to modify something of the screen from the thread, you need to do it from the FX Thread, the Task function call is outside the FX Thread, for that reason the screen it isn't freeze. But all the bind elements will occur in the FX Thread, so are safe to modify the GUI.
If you want to modify safely the GUI from a not FX Thread, just do:
Platform.runLater(new Runnable() {
#Override
public void run() {
//Safe modification in the FX Thread
}
});
Also take a look on concurrency in JavaFX2. This explain more deeply the concurrency, Service, Task...
Hope it helps!