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;
}
}
Related
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();
}
I'm using a timeline in JavaFX to do a countdown of a Label:
timeline.setCycleCount(6);
timeline.play();
And I want to return a value after the timeline has finished:
return true;
However, it seems that the value is getting returned immediately and the timeline runs parallel. How can I wait until the timeline has finished its countdown and then return the value without blocking the timeline?
EDIT:
To make it more clear, I already tried:
new Thread(() -> {
timeline.play();
}).start();
while(!finished){ // finished is set to true, when the countdown is <=0
}
return true;
(This solution doesn't update the countdown.)
EDIT 2:
Here is a minimal, complete and verifiable example:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountdownTest extends Application {
private Label CountdownLabel;
private int Ctime;
#Override
public void start(Stage primaryStage) {
CountdownLabel=new Label(Ctime+"");
StackPane root = new StackPane();
root.getChildren().add(CountdownLabel);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Countdown Test");
primaryStage.setScene(scene);
primaryStage.show();
Ctime=5;
if(myCountdown()){
CountdownLabel.setText("COUNTDOWN FINISHED");
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public boolean myCountdown(){
final Timeline timeline = new Timeline(
new KeyFrame(
Duration.millis(1000),
event -> {
CountdownLabel.setText(Ctime+"");
Ctime--;
}
)
);
timeline.setCycleCount(6);
timeline.play();
return true;
}
}
You can see that it first shows "COUNTDOWN FINISHED" and counts down to 0 instead of starting with the countdown and counting down to "COUNTDOWN FINISHED".
As a Timeline inherits from Animation, you can use setOnFinished to define an action to occur at the end of the timeline.
timeline.setCycleCount(6);
timeline.play();
timeline.setOnFinished(event -> countdownLabel.setText("COUNTDOWN FINISHED"));
If you really want to wait till the timeline finished, you can use a CountDownLatch or a Semaphore along with setOnFinished. Something like the following should work:
CountDownLatch latch = new CountDownLatch(1);
timeline.setCycleCount(6);
timeline.setOnFinished(event -> latch.countDown());
timeline.play();
latch.await();
return true;
You are trying to wait in the one thread for the result of the work in the another thread. That is what synchronisation was created for! E.g. java.util.concurrent.Semaphore:
public boolean waitForTimeline() {
Semaphore semaphore = new Semaphore(0);
System.out.println("starting timeline");
Timeline t = new Timeline();
t.getKeyFrames().add(new KeyFrame(Duration.seconds(2)));
t.setOnFinished((e)-> {
System.out.println("releasing semaphore");
semaphore.release();
});
t.play();
System.out.println("waiting for timeline to end");
try {
semaphore.acquire();
} catch (InterruptedException ex) {
ex.printStackTrace();
return false;
}
return true;
}
But note, that you can't run this method on "JavaFX Application Thread" as it will block UI updates. Run it on a separate thread:
new Thread(()-> {
System.out.println("returned from timeline with " + waitForTimeline());
}).start();
or, better, instead create a listener with the logic you do after return and call that listener from t.setOnFinished(). For your example, it will be:
public void myCountdown(Runnable onSuccess){
//...
timeline.setOnFinished((e)-> {
onSuccess.run();
});
}
and corresponding call:
myCountdown(()->{
CountdownLabel.setText("COUNTDOWN FINISHED");
});
Use Animation's setOnFinished in addition to the KeyFrame's setOnFinished that you have (a Timeline is an Animation). Iv'e modified your MCVE to show how to do that for your case:
public class CountdownTest extends Application {
private Label countdownLabel;
private int ctime = 5;
#Override
public void start(Stage primaryStage) {
countdownLabel = new Label();
StackPane root = new StackPane();
root.getChildren().add(countdownLabel);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Countdown Test");
primaryStage.setScene(scene);
primaryStage.show();
myCountdown();
}
public void myCountdown() {
final Timeline timeline = new Timeline(new KeyFrame(Duration.millis(1000),
event -> {
countdownLabel.setText(ctime + "");
ctime--;
}
));
timeline.setCycleCount(ctime + 1);
timeline.setOnFinished(e -> countdownLabel.setText("COUNTDOWN FINISHED"));
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
On each cycle, the KeyFrame's setOnFinished will be executed, reducing the count by 1. When the whole animation finishes (all of its cycles), the Timeline's setOnFinished will be executed.
Notes:
Use the ctime variable to calculate the number of cycles so that they match.
Use the Java naming conventions: identifiers (names) start with lowercase.
Indent your code properly so that it's easier to read.
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.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);
Good day,
I try to level my JavaFx skills and I am programming a little media player.
Hence, there is a slider object which will be responsible to show the user the elapsed time (and seek the track position if the user wants to).
To monitor the current track position I use another thread which will ask for the elapsed time each 100 ms. It updates a DoubleProperty, which the slidervalue will be binded to.
This works, however CPU usage increases to 70-80 % if I do so.
Snippet:
Task task = new Task<Void>() {
#Override public Void call() throws InterruptedException {
double time=0;
double dummy=0;
while(true) {
try{
if(time == tmp.getPlaytime()){
dummy += 0.1;
}else{
dummy = time;
}
updateProgress(dummy, tracklength.getValue());
time = tmp.getPlaytime();
Thread.sleep(100);
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
}
};
trackposition.bind(task.progressProperty());
new Thread(task).start();
tmp is the object which will provide the elapsed time, and the method can raise an exception, hence the try/catch block.
tracklength is a DoubleProperty which provides the Length of the track in seconds.
Since I only have a time resolution of a second within the method in tmp I use the dummy variable to make a smooth slider animation.
The idea for this thread - approach, was taken from Oracle Tutorial
I know that multithreading and GUI is a difficult topic, but do you have any idea how drop the CPU usage ?
(Will the runLater() approach work better in your opinion ? Since runLater means I have no idea when it will be executed, I can not provide a "smooth" transition and wouldn't "want" to use it then )
Thank you
I've created this short sample to test your task, and it's working flawlessly, no relevant CPU usage.
private volatile boolean running = true;
#Override
public void start(Stage primaryStage) {
final Media song = new Media(<Media URL>);
MediaPlayer media_player = new MediaPlayer(song);
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws InterruptedException {
double time=0;
double dummy=0;
while(running) {
try{
if(time == media_player.getCurrentTime().toSeconds()){
dummy += 0.1;
}else{
dummy = time;
}
updateProgress(dummy, media_player.getTotalDuration().toSeconds());
time = media_player.getCurrentTime().toSeconds();
Thread.sleep(100);
}
catch(Exception e){
System.out.println(e.getMessage());
running=false;
}
}
return null;
}
};
Slider slider = new Slider(0,100,0);
slider.valueProperty().bind(task.progressProperty().multiply(100));
media_player.setOnReady(() -> {
new Thread(task).start();
media_player.play();
});
StackPane root = new StackPane();
root.getChildren().add(slider);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
Try to run it, and check if it works, and how is the CPU usage.