JavaFX - Refresh Label [duplicate] - java

This question already has an answer here:
JavaFx - Updating GUI
(1 answer)
Closed 6 years ago.
Can you explain me how can I refresh value in Label?
In initialize I bind the text of the Label to a StringProperty. Here it is ok.
I have Button, and on button press I want to update the Label value in every iteration step.
But I can see only the final value. Why?
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) throws InterruptedException {
for(int i=0;i<1001;i++){
try {
Thread.sleep(1);
} catch (InterruptedException ie) {
//Handle exception
}
this.value.setValue(i+"");
}
}
// Bind
private StringProperty value = new SimpleStringProperty("0");
#Override
public void initialize(URL url, ResourceBundle rb) {
// Bind label to value.
this.label.textProperty().bind(this.value);
}

When you call Thread.sleep(1); you actually stop the JavaFX Application Thread (GUI Thread), therefore you prevent it to update the GUI.
What you basically need is a background Task which actually stops for a certain amount of time, then updates the GUI on the JavaFX Application Thread by calling Platform.runLater before it goes to sleep again.
Example:
public class MyApplication extends Application {
private IntegerProperty value = new SimpleIntegerProperty(0);
#Override
public void start(Stage primaryStage) {
try {
HBox root = new HBox();
Scene scene = new Scene(root, 400, 400);
Label label = new Label();
Button button = new Button("Press Me");
button.setOnAction(event -> {
// Background Task
Task<Void> task = new Task<Void>() {
#Override
protected Void call() {
for (int i = 0; i < 1001; i++) {
int intVal = i;
try {
Thread.sleep(1);
} catch (InterruptedException ignored) {
}
// Update the GUI on the JavaFX Application Thread
Platform.runLater(() -> value.setValue(intVal));
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
});
label.textProperty().bind(value.asString());
root.getChildren().addAll(button, label);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Only thing left is to update the button callback.

Related

JavaFX show object at runtime

i have a javafx application, i need to show/edit/manipulate some objects at runtime.
For example i have an anchorPane, when i click a button i need to set the pane visible, and during the execution of the logic, set some labels to visible.
here's my code:
//MyController.java
public class MyController extends AnchorPane {
#FXML
private AnchorPane myPane;
#FXML
private Label label1;
#FXML
private Label label2;
#FXML
private Button button1;
private Main application;
public void setApp(Main application) {
this.application = application;
}
public void initialize()
{
myButton.setOnAction(new EventHandler<ActionEvent>()
{
#override
public void handle(ActionEvent arg0)
{
myPane.setVisile(true);.
//long run action
label1.setVisible(true);
//long run action
label2.setVisibile(true);
//long run action
button1.setText("myText");
//long run action
button1.setVisibile(true);
//end of operations
}
)};
}
}
I don't understand why the GUI freeze and all the elements are shown together at the end of the operations, I tried to see something about javafx thread management but I couldn't implement a satisfactory solution.. can someone help me?
Thank you all.
Here's a solution leveraging the java.util.concurrent and javafx.concurrent APIs:
Executor exec = Executors.newSingleThreadedExecutor();
// ...
button.setOnAction(event -> {
myPane.setVisible(true);
Task<Void> task1 = createTask(this::doTask1, () -> label1.setVisible(true));
Task<Void> task2 = createTask(this::doTask2, () -> label2.setVisible(true));
Task<Void> task3 = createTask(this::doTask3, () -> button1.setText("myText"));
Task<Void> task4 = createTask(this::doTask4, () -> button1.setVisible(true));
List.of(task1, task2, task3, task4).forEach(exec::execute);
});
// ...
private Task<Void> createTask(Runnable longRunningProcess, Runnable onSucceeded) {
Task<Void> task = new Task<>() {
#Override
public Void call() {
longRunningProcess.run();
return null ;
}
};
task.setOnSucceeded(e -> onSucceeded.run());
return task ;
}
private void doTask1() {
// long running operation...
}
private void doTask2() {
// long running operation...
}
private void doTask3() {
// long running operation...
}
private void doTask4() {
// long running operation...
}
Here is a quick runnable example in writing my comment I realized I glossed over the fact that it is not already on a different thread and was reminded when I read VGR's comment here is a runnable example I used just a new Thread for a simple demonstration but would recommend a Task or a Service. Note you can see that the Main Thread is not freezing by the fact that you can still click the RadioButtons
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
VBox vBox = new VBox();
vBox.setPrefSize(200, 200);
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().add(new Label("Main APP"));
Pane pane = new Pane(new Label("THIS IS INSIDE THE PANE"));
vBox.getChildren().add(pane);
Button button = new Button("Activate Pane Cloaking");
button.setOnAction(event -> {
new Thread(() -> {
simulateCodeRunning();//5 sec
Platform.runLater(() -> pane.setVisible(!pane.isVisible()));
if (pane.isVisible())
Platform.runLater(() ->button.setText("Deactivate Cloaking"));
else
Platform.runLater(() ->button.setText("Activate Pane Cloaking"));
}).start();
});
vBox.getChildren().add(button);
ToggleGroup radioGroup = new ToggleGroup();
RadioButton radioButton1 = new RadioButton("Click Me");
radioButton1.setToggleGroup(radioGroup);
RadioButton radioButton2 = new RadioButton("Click Me");
radioButton2.setToggleGroup(radioGroup);
vBox.getChildren().addAll(radioButton1, radioButton2);
primaryStage = new Stage();
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
private void simulateCodeRunning() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

JavaFX: Prompt user based on Task, and pass back result?

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();
}

How to call JavaFX controller from non JavaFX thread and wait for the GUI changes to finish (finish playing video)?

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();
}

JavaFX Auto Open new Window

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);

JavaFX Multithreading Issue [duplicate]

This question already has an answer here:
JavaFx2 IllegalStateException with Label.setText
(1 answer)
Closed 6 years ago.
I'm currently making an application in which we want to use multithreading to display the flashing label "loading" for a certain period of time after logging in, before continuing on to the next page. Here is my current progress:
public class LoadingController implements Initializable {
#FXML
private Label loadingLabel;
boolean ready = false;
public void setReady() {
System.out.println("now I'm ready");
ready = true;
}
public void showLabel() {
this.loadingLabel.setVisible(true);
}
public void hideLabel() {
this.loadingLabel.setVisible(false);
}
public void goToPage2() {
try {
Parent root = FXMLLoader.load(getClass().getResource("Page2.fxml"));
Scene scene = new Scene(root);
Stage stage = Assignment.getStage();
stage.setScene(scene);
} catch (IOException ex) {
Logger.getLogger(LoadingController.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
System.out.println("In loading page");
// TODO launch thread
Thread2 thread = new Thread2(this);
thread.start();
}
}
public class Thread2 extends Thread {
private LoadingController con;
public Thread2(LoadingController con) {
this.con = con;
}
public void run() {
System.out.println("Hello from a thread!");
try {
for (int i = 0; i < 20; i++) {
con.hideLabel();
Thread.sleep(100);
con.showLabel();
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
con.setReady();
}
}
I currently get this error relating to line
Scene scene = new Scene(root);
Exception in thread "Thread-6" java.lang.IllegalStateException: Not on FX application thread.
Would anyone be able to provide some guidance on this issue?
Thank you
The guidance is very simple - read the relevant documentation about JavaFX concurrency.
https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm
The exception clearly tells you what is wrong. You try to create a new Scene from the wrong thread. SceneGraph manipulations are only allowed from the JavaFX application thread.

Categories