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.
Related
I'm trying to implement a simple JavaFX GUI - Meaning a few buttons, a text area, a few circles (as lights) and a few labels.
Note that none of the functionality for these items has been created yet. So all these items are essentially hollow. Note this is my first JavaFX application (I've always worked in Swing).
So running in Eclipse on my i7 laptop has no problems and runs flawlessly. When I try to run it on the target machine (specs below) I get about a 15 second long non-responsive window. Once the gui comes up, any clicks on any elements in the window causes ~10seconds of CPU spiking up to 50ish%.
Once again there is no code behind any of the buttons or lights or text area yet. Is JavaFX just not meant to be on such a low end system?
Specs:
Windows Embedded 32bit (6.1)
Intel Atom E680 # 1.6Ghz
2GB Ram
DX11
Atom E6xx Embedded Media and Graphics
Code is below if anyone needs it.
Main:
public static void main(String[] args)
{
// Initialize GUI
gui = RecorderFXApplication.getGUIInstance();
guiCtrl = gui.getFXMLController();
}
Application:
public class RecorderFXApplication extends Application
{
/**
* Standard error logger for log4j2
*/
static Logger errorLog = LogManager.getLogger(RecorderFXApplication.class.getName());
private static RecorderFXApplication instance = null;
private FXMLLoader fxmlLoader;
public RecorderFXApplication()
{
super();
}
#Override
public void start(Stage primaryStage) throws Exception
{
fxmlLoader = new FXMLLoader(getClass().getResource("/RecorderGUI.fxml"));
try
{
Pane root = (Pane) fxmlLoader.load();
Scene scene = new Scene(new Group(root));
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.titleProperty().set("Faceboss Database Recorder");
instance = this;
} catch (IOException exception)
{
errorLog.error("Error loading fxml for view: " + exception);
}
}
#Override
public void stop() throws Exception
{
super.stop();
System.exit(0);
}
/**
* #return the instance
*/
public static RecorderFXApplication getGUIInstance()
{
if (instance == null)
{
new Thread(new Runnable()
{
#Override
public void run()
{
Application.launch(RecorderFXApplication.class);
}
}).start();
}
while (instance == null)
{
try
{
Thread.sleep(1);
} catch (InterruptedException e)
{
errorLog.error("Error getting instance " + e);
}
}
return instance;
}
public RecorderFXController getFXMLController()
{
while (fxmlLoader.getController() == null)
{
try
{
Thread.sleep(1);
} catch (InterruptedException e)
{
errorLog.error("Error getting controller " + e);
}
}
return fxmlLoader.<RecorderFXController> getController();
}
}
Controller:
public class RecorderFXController implements Initializable
{
/**
* Standard error logger for log4j2
*/
static Logger errorLog = LogManager.getLogger(RecorderFXController.class.getName());
#FXML
private TextArea logWindow;
#FXML
private Button startRecButton;
#FXML
private Button stopRecButton;
#FXML
private Button pauseRecButton;
#FXML
private Button exportButton;
#FXML
private Circle redLight;
#FXML
private Circle yellowLight;
#FXML
private Circle greenLight;
#Override
public void initialize(URL arg0, ResourceBundle arg1)
{
}
}
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 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);
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.
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;
}
}