I have a simple JavaFX GUI that fires a background task on button click. This task continuously updates a TextArea with its latest progress messages. I have demonstrated how I solved this below. The issue arises when the task runs into an error, and requires a decision from the user on how to proceed. My goal is to have this decision made via an Alert, with the user choosing Yes or No. I've been unable to achieve this functionality, though. Here is what I have attempted so far:
Create an Alert in the JavaFX main thread, pass it to the script, and call showAndWait. This resulted in the error indicating I am not in a JavaFX thread.
UpdateMessage() etc. Extending the script as a Task, I keep running into a NullPointerException.
Creating a new JavaFX instance from the script.
Thank you for your help!
Button creation with EventHandler:
private Button createButton() {
Button btn = new Button();
btn.setText("Run");
btn.setPrefWidth(100);
EventHandler<ActionEvent> buildWindow = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
TextArea output = buildCenterTextArea();
Task task = new Task<Void>() {
#Override public Void call() {
callScript(output); // Calls script
return null;
}
};
new Thread(task).start();
}
};
btn.setOnAction(buildWindow);
return btn;
}
private void buildCenterTextArea() {
// Builds a text area which the script updates with status
TextArea output = new TextArea();
output.setEditable(false);
this.borderpane.setCenter(output);
return output
}
In my script, I update the text by doing the following:
output.setText(statusText+ "\n" + newStatus);
The background thread can be kept busy waiting. This means you can create a CompletableFuture, use Platform.runLater to create an alert and displaying it using showAndWait and after that filling the future with the results. Just after this call on the background thread wait for the result using Future.get.
The following example generates random numbers between 0 and 9 (inclusive) and prints 0-8 to the TextArea. 9 is a simulated error and the user is asked, if the task should be continued.
#Override
public void start(Stage stage) throws IOException {
TextArea ta = new TextArea();
Thread thread = new Thread(() -> {
Random rand = new Random();
while (true) {
int i = rand.nextInt(10);
if (i == 9) {
CompletableFuture<ButtonType> future = new CompletableFuture<>();
// ask for user input
Platform.runLater(() -> {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setContentText("An error occured. Continue?");
future.complete(alert.showAndWait().orElse(ButtonType.CANCEL)); // publish result
});
try {
if (future.get() == ButtonType.CANCEL) { // wait for user input on background thread
break;
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
break;
}
} else {
Platform.runLater(() ->ta.appendText(Integer.toString(i) + "\n"));
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
});
thread.setDaemon(true);
thread.start();
Scene scene = new Scene(new VBox(ta));
stage.setScene(scene);
stage.show();
}
Related
I have 2 classes: Controller (JavaFX controller) and MachineController (not JavaFX thread). Sometimes MachineController sent message to Controller using method setMessage.
Method setMessage(String str) should update GUI by adding String to the List and on the Label, also if it is necessary, I shoul play the video or show Images, but i must wait for end of playing video or end of showing Image (it shows some time (for example, 3-4 seconds)).
I have used Task and Platform.runLater(). But if i use Task Images are shown only sometimes, and video wasn't played at all. If i use Platform.runLater i couldn't wait to end of playing video or shoeing image, because it start in the futere.
Controller
public void setMessage(final String str) {
Task<Void> task = new Task<>() {
boolean test = true;
#Override
protected Void call() throws Exception {
while (test) {
currentCommandLabel.setText(str);
commands.add(str);
Executable show = analyze.getExec(str);
show.exec(pane);
Thread.sleep(1000);
}
return null;
}
};
Thread thread = new Thread(task);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Executable
interface Executable {
void exec(GridPane pane);
}
Analize
Executable getExec(String string) {
return panel -> {
cleanZeroCell(pane1);
File fileImage = new File("<path to image file>");
Image image = new Image(fileImage.toURI().toString());
imageView.setImage(image);
panel.add(imageView, 0, 0);
};
}
Also I have tried to used setMessage like this:
public void setMessage(final String str) {
Platform.runLater(() -> {
currentCommandLabel.setText(str);
commands.add(str);
});
Executable show = analyze.getExec(str);
Platform.runLater(() -> show.exec(pane));
}
You can use a CountDownLatch to wait for the JavaFX application thread on a non-application thread. The following example uses a animation instead of a video or "image showing", but you could easily use the MediaPlayer.onEndOfMedia event instead of Animation.onFinished event:
private void startAnimation(Button button, CountDownLatch latch) {
TranslateTransition animation = new TranslateTransition(Duration.seconds(2), button);
animation.setByX(100);
animation.setOnFinished(evt -> latch.countDown());
animation.play();
}
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Animate");
btn.setOnAction((ActionEvent event) -> {
new Thread(() -> {
try {
// simulates some work prior to modifying the ui
Thread.sleep(2000);
} catch (InterruptedException ex) {
}
CountDownLatch latch = new CountDownLatch(1);
// start animation on application thread
Platform.runLater(() -> startAnimation(btn, latch));
try {
// wait for application thread to count down the latch
latch.await();
System.out.println("Done");
} catch (InterruptedException ex) {
ex.printStackTrace(System.err);
}
}).start();
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
I've got a task-control GUI with an 'Abort' button which invokes some cancellation code (works fine). However, I'd like to sync the button's disabled state to whether a task is running or not so the option is disabled if the task isn't running. I can check the state of the task via its Boolean Future.isDone() method but I'd like to have JavaFX manage the state automatically via a bound property. I can't figure out how to establish the binding, though, or set up a ChangeListener on a method. Can anyone advise on how best to accomplish?
Edit: I think this question ultimately distills down to "How do I wrap Future.isDone() to make it an Observable?"
Guidance very much appreciated.
Use Task. You can observe the state property to modify the Button's disabled property:
private ExecutorService service;
#Override
public void stop() throws Exception {
service.shutdownNow();
}
#Override
public void init() throws Exception {
service = Executors.newSingleThreadScheduledExecutor();
// service = new ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
}
#Override
public void start(Stage primaryStage) {
Button startButton = new Button("Start");
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
Thread.sleep(5000);
System.out.println("task finished");
return null;
}
};
Button cancelButton = new Button("cancel");
cancelButton.setOnAction(evt -> task.cancel());
cancelButton.setDisable(true); // button disabled before submitting task
startButton.setOnAction((ActionEvent event) -> {
startButton.setDisable(true);
// cancel Button enabled until task is succeeded/failed/was cancelled
cancelButton.disableProperty().bind(Bindings.createBooleanBinding(() -> {
switch (task.getState()) {
case CANCELLED:
case FAILED:
case SUCCEEDED:
return true;
default:
return false;
}
}, task.stateProperty()));
// simulate some work to be done by the service
service.submit(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
}
System.out.println("runnable finished");
});
service.submit(task);
});
// show state as text
Text text = new Text();
text.textProperty().bind(task.stateProperty().asString());
VBox root = new VBox(10, startButton, cancelButton, text);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
Solved, or good enough. GUI controls bound to a SimpleBooleanProperty. Constructor kicks off a background thread Runnable that polls isDone() in an endless loop with half-second sleep and updates the bound SBP. Thank you again, fabian, for pointing me to a proper solution for future reference.
I have a javafx application. I programmed it such that the application starts with a progress bar that runs to completion. On completion, The window closes and a login window opens. After you sign in, the login window closes and the main window opens.
But that is not what is happening in my application.
I made a task that runs the progressbar from 0 - 100 and I made the task to run on a Thread. When the task is completed, the window closes and login window opens.
The problem I am encountering is that when I sign in using the login window, the main window opens but the login window didn't close.
I wrote a method to close the login window when the main window opens, but That didnt work.
Please what am I missing?
This is the task class
public class TaskClass implements Runnable {
#Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
progress.setProgress(i / 100.0);
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(RunningController.class.getName()).log(Level.SEVERE, null, ex);
}
}
Platform.runLater(new Runnable() {
#Override
public void run() {
// this is the method that closes the window on which the progress bar is running
closed(clos);
// this is the method that opens the login
windowloadwindow("/inventory/management/login/InventoryManagementlogin.fxml", "DeMak Inventory");
}
});
}
}
This loads the progress bar on a thread
Thread thread;
#FXML
private JFXProgressBar progress;
#Override
public void initialize(URL url, ResourceBundle rb) {
progress.setProgress(0.0);
Runnable tasks = new TaskClass();
thread = new Thread(tasks);
thread.start();
}
After you must have signed in using the login window, This is meant to happen
loadwindow("/inventory/management/ui/Main/main.fxml", "Deemax Inventory"); // a method that opens the main window
closed(closew); // a method that closes the login window
In order to synchronize Platform.runLater() code and background thread code you could use the CountDownLatch Class as following:
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
CountDownLatch sync = new CountDownLatch(1);
Platform.runLater(()->{
// GUI code
// ...
// end of GUI code
sync.countDown();
});
sync.await(); // This method will wait for sync.countDown() to be executed
// ...
// code here will be executed after GUI code has finished
// ...
return null;
}
};
Thread thread = new Thread(task);
thread.start();
But, instead you can solve your problem much easier like this:
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
for (int i = 0; i < 100; i++) {
final int finalCounter = i;
Platform.runLater(()->{
try {
progress.setProgress(finalCounter / 100.0);
} catch (InterruptedException e) {
Logger.getLogger(RunningController.class.getName()).log(Level.SEVERE, null, ex);
}
});
Thread.sleep(100);
}
return null;
}
#Override
protected void succeeded() {
// code here will be executed after the thread had been completed
Platform.runLater(()->{
new Alert(Alert.AlertType.INFORMATION, "Thread Succeeded!").show();
});
closed(clos); // this is the method that closes the window on which the progress bar is running
loadwindow("/inventory/management/login/InventoryManagementlogin.fxml", "DeMak Inventory"); // this is the method that opens the login window
super.succeeded();
}
#Override
protected void failed() {
Platform.runLater(()->{
new Alert(Alert.AlertType.ERROR, "Thread Failed!").show();
});
super.failed();
}
};
Thread thread = new Thread(task);
thread.start();
This is my closed method
private void closed(Node nor) {
Stage stage = (Stage)nor.getScene().getWindow();
stage.close();
}
This is my node object.... It is a vbox containing an X image
#FXML
private VBox clos;
so I pass the node to the method like this.
closed(clos);
I have A.fxml and B.fxml. A runing with Java Application override start method. I want to every 40 min in loop(5 times) { open new stage B.fxml and wait stage.close, if stage close continue loop open new stage B fxml. Loop this five times. I try timer timertask i could not. I try JavaFX Service i could not. I create Mythread extend Thread object. This time i could not control loop for next stage. When for statement start opening 5 stage. But i want to loop wait for currentstage is close then go next loop. This is my fail code;
public class Driver extends Application {
public static Stage stage;
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource(View.SETTINGS));
Parent root = loader.load();
Scene scene = new Scene(root);
stage = primaryStage;
stage.setScene(scene);
stage.setTitle("Info Library");
stage.setResizable(false);
stage.show();
RandomQuestionThread thread = new RandomQuestionThread();
if (DBContext.settings.isAbbreviation() || DBContext.settings.isTranslation()) {
thread.start();
}
}
public static void main(String[] args) throws InterruptedException {
DBContext.settings = DBContext.getInstance().settings().getSettings();
launch(args);
HibernateUtil.getSessionFactory().close();
}
}
public class RandomQuestionThread extends Thread {
Thread randomThread = new Thread(this);
private String fxml;
private static String TITLE;
#Override
public void run() {
while (true) {
try {
Thread.sleep(DBContext.settings.getAutoQuestionTime() * 6000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
for (int i = 0; i<DBContext.settings.getAutoQuestionCount(); i++) {
randomFxml();
Platform.runLater(()->{
Parent root = null;
try {
root = new FXMLLoader(getClass().getResource(fxml)).load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.setTitle(TITLE);
stage.show();
System.out.println(currentThread().getName());
});
}
}
}
private void randomFxml() {
int start = 0;
if (DBContext.settings.isTranslation() && DBContext.settings.isAbbreviation()) {
start = new Random().nextInt(2);
} else if (DBContext.settings.isTranslation()) {
start = 1;
}
switch (start) {
case 0:
fxml = View.ABBREVIATION;
break;
case 1:
fxml = View.TRANSLATION;
break;
default:
break;
}
if (start == 0) {
TITLE = "KISALTMA SORUSU";
} else TITLE = "ÇEVİRİ SORUSU";
}
}
I need to work more Java multi threads. But after fix this problem. Please explain where I'm doing wrong. In loop write console currentThread name console result "Java Apllication Thread". But i set my thread name "MyThread". I'm so confused.My brain gave blue screen error.
You've put your System.out.println(currentThread().getName()) statement into Platform.runLater(), which means that it will be executed on JavaFX Application Thread (see JavaDoc).
Regarding your question about scheduling some task to repeat fixed number of times with predefined rate, this post could help you.
In loop write console currentThread name console result "Java Apllication Thread". But i set my thread name "MyThread". I'm so confused.
Using Platform.runLater you schedule the Runnable to be executed on the javafx application thread instead of the current thread which allows you to modify the UI, but also results in the current thread being the javafx application thread instead of the thread you call Platform.runLater from...
If you want to continue the "loop" after the window has been closed, you should schedule opening the next window after the last one has been closed. Stage.showAndWait() is a convenient way to wait for the stage to be closed.
For scheduling I'd recommend using a ScheduledExecutorService:
private ScheduledExecutorService executor;
#Override
public void stop() throws Exception {
// stop executor to allow the JVM to terminate
executor.shutdownNow();
}
#Override
public void init() throws Exception {
executor = Executors.newSingleThreadScheduledExecutor();
}
#Override
public void start(Stage primaryStage) {
Button btn = new Button("Start");
btn.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
// just display a "empty" scene
Scene scene = new Scene(new Pane(), 100, 100);
Stage stage = new Stage();
stage.setScene(scene);
// schedule showing the stage after 5 sec
executor.schedule(new Runnable() {
private int openCount = 5;
#Override
public void run() {
Platform.runLater(() -> {
stage.showAndWait();
if (--openCount > 0) {
// show again after 5 sec unless the window was already opened 5 times
executor.schedule(this, 5, TimeUnit.SECONDS);
}
});
}
}, 5, TimeUnit.SECONDS);
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
I fix this. I used Timer and TimeTask in my main controller init method. And its work. But same code in app start method or in mian method stage didnt wait. I used stageshowandwait() method but thread didnt wait. But same code woked in main controller init method. Why i dont know.
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
#Override
public void run() {
Platform.runLater(()->{
for (int i = 0; i<4; i++) {
Parent root = null;
try {
root = new FXMLLoader(getClass().getResource(View.ABBREVIATION)).load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.setTitle("deneme");
stage.showAndWait();
}
});
}
};
timer.schedule(timerTask, 6000);
I'm using a thread in JavaFX to repeat my code after an interval (initially 1s), but I want to be able change the interval that the thread is using to 500ms or 333ms based on user choice (I have a button in a menu bar to change for each choice). I did tried things like shutDown() if the user clicks on one of the buttons and initiate it again with the new value, but didn't work. Any ideas?
Here's the relevant part of my code:
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
exec.scheduleAtFixedRate(() -> {
//refresh users, line and "guiche"
updateFila(usuarios, guiches, fila);
updateGuiche(guiches, fila, graphicsContext);
turno++;
//ends the code after the end of the line
if (done) {
exec.shutdown();
}
}, 0, 1000, TimeUnit.MILLISECONDS); //This is interval that I need to change after user choice
I know that I'm executing scheduleAtFixedRate() right now, but it was just to see if the logic was fine.
Additionally, I need to pause, resume and reset the thread, all based on user click.
You could use a Timeline to execute a event handler every second and set the rate at which the animation runs to the number of times the update should happen per second, i.e. 2 or 3...
In the following example I use 5 instead of 3 for a more recognizable effect:
#Override
public void start(Stage primaryStage) {
Line line = new Line(25, 125, 125, 125);
Rotate rotate = new Rotate(0, 125, 125);
line.getTransforms().add(rotate);
ToggleButton btn = new ToggleButton();
btn.textProperty().bind(Bindings.when(btn.selectedProperty()).then("5 Hz").otherwise("2 Hz"));
StackPane.setAlignment(btn, Pos.BOTTOM_LEFT);
// rotate by one 60th of a full rotation each time
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), evt -> rotate.setAngle((rotate.getAngle() + (360d / 60d)) % 360)));
timeline.setCycleCount(Animation.INDEFINITE);
// rate depends on button state
timeline.rateProperty().bind(Bindings.when(btn.selectedProperty()).then(5d).otherwise(2d));
Pane linePane = new Pane(line);
linePane.setMinSize(250, 250);
linePane.setMaxSize(250, 250);
StackPane root = new StackPane();
root.getChildren().addAll(linePane, btn);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
timeline.play();
primaryStage.show();
}
The binding is simply an example of setting the update frequency. You could of course use different means to assign this value, e.g.
ComboBox<Duration> combo = new ComboBox<>();
Duration initial = Duration.seconds(1);
combo.getItems().addAll(initial, Duration.seconds(1/3d), Duration.seconds(1/2d));
combo.setValue(initial);
combo.valueProperty().addListener((observable, oldValue, newValue) -> timeline.setRate(1/newValue.toSeconds()));
If you use only single thread you can create your own implementation based on classic thread.
public class Worker extends Thread {
private static final Logger logger = Logger.getLogger(Worker.class);
private volatile int delayInSec = 1;
private CountDownLatch latch;
private final int STARTED = 0;
private final int STOPPED = 1;
private volatile int state = STOPPED;
public Worker(){}
#Override
public void run() {
logger.debug("enter to execution method");
while (!isInterrupted()) {
// stop if needed (it's additional feature)
if (state == STOPPED) {
logger.debug("stopped and locked");
try {
latch = new CountDownLatch(1);
latch.await();
} catch (InterruptedException e) {
logger.warning("got interruption while waiting for action ", e);
break;
}
logger.debug("awake");
}
// do your stuff here
try {
// then delay
logger.debug("go to sleep for %s sec.",delayInSec);
latch = new CountDownLatch(1);
latch.await(delayInSec, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.warning("got interruption while waiting for action ", e);
break;
}
}
logger.debug("exit from execution method");
}
public void startJob(){
state = STARTED;
logger.debug("started");
if (latch!=null)
latch.countDown();
}
public void stopJob(){
state = STOPPED;
logger.debug("stopped");
}
public void shutdown(){
logger.debug("shutdown");
interrupt();
}
public void changeDelay(int delayInSec) {
logger.debug("set new delay %s", delayInSec);
this.delayInSec = delayInSec;
}
}