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);
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 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();
}
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.
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.
The application reacts on actions which occur on gamepad. When button is pressed something happens on UI. But I ran at the issue with app hangs up or "java.lang.IllegalStateException: Not on FX application thread" exception.
In order to fix it I tried the following approaches: Platform.runLater() and Task usage. But it didn't help.
Here is the problem code:
public class GamepadUI extends Application{
private static final int WIDTH = 300;
private static final int HEIGHT = 213;
private Pane root = new Pane();
private ImageView iv1 = new ImageView();
private boolean isXPressed = false;
#Override
public void start(Stage stage) throws Exception {
initGUI(root);
Scene scene = new Scene(root, WIDTH, HEIGHT);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public void pressBtn() {
if(!isXPressed) {
iv1.setVisible(true);
isXPressed = true;
}
}
public void releaseBtn() {
if(isXPressed) {
iv1.setVisible(false);
isXPressed = false;
}
}
private void initGUI(final Pane root) {
Image image = new Image(Props.BUTTON);
iv1.setImage(image);
iv1.setLayoutX(198);
iv1.setLayoutY(48);
iv1.setVisible(false);
root.getChildren().add(iv1);
runTask();
}
public void runTask() {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
};
new Thread(task).start();
}
public static void main(String[] args) {
launch(args);
}
public void initStubGamepad() {
Random rnd = new Random();
try {
while (true) {
if (rnd.nextInt(30) == 3) {
pressBtn();
} else if (rnd.nextInt(30) == 7) {
releaseBtn();
}
}
} catch (Exception ex) {
System.out.println("Exception: " + ex);
}
}
}
initStubGamepad() emulates gamepad buttons activity polling. When user presses any button (rnd.nextInt(30) == 3) - an image appears on the UI. When user releases that button (rnd.nextInt(30) == 7) - an image disappears from the UI.
In case above java.lang.IllegalStateException: Not on FX application thread occurs. If you change runTask() to something like this:
Platform.runLater(new Runnable() {
#Override
public void run() {
initStubGamepad();
}
});
Then app will hang or even main UI won't appear at all, but gamepad activity continues.
What I want is just to show/hide different images when some activity is detected on gamepad (btw, there's no way to monitor gamepad activity except for gamepad polling in an infinite loop). What did I wrong
Explanation
In the first scenario, when you are using
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
}
Inside initStubGamepad(), which is running on a Task, you are trying to update the UI components inside pressBtn() and releaseBtn() methods, which is why you are facing a
java.lang.IllegalStateException: Not on FX application thread
because all the UI updates must occur on the JavaFX thread
In the second scenario, when you are using
Platform.runLater(new Runnable() {
#Override
public void run() {
initStubGamepad();
}
});
the UI doesnt appear, because you have an infinite loop inside the initStubGamepad(), which puts the JavaFX application thread run on an infinite loop
Solution
By the time you have reach here, you must have already found the solution. In case you haven't, try try to put the update the Javafx components on the UI thread. So, instead of calling initStubGamepad() inside Platform.runLater, try calling pressBtn() and releaseBtn() inside it.
Try using
while (true) {
if (rnd.nextInt(30) == 3) {
Platform.runLater(() -> pressBtn());
} else if (rnd.nextInt(30) == 7) {
Platform.runLater(() -> releaseBtn());
}
}
or you may also use
public void pressBtn() {
if(!isXPressed) {
Platform.runLater(() -> iv1.setVisible(true));
isXPressed = true;
}
}