Not on FX application thread; currentThread = AWT-EventQueue-0 - java

So I'm trying to make my JavaFX Application visible, if I press CTRL + Alt + D (I'm using jkeymaster). But everytime I write Stage.show(); in my HotKeyListener I get Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0 (line 7) (I also tested to show a file chooser in my hot key listener and outside of the listener and if I do the second thing I get no error). And also if I just put System.out.println("Test") in my hot key listener without the other things it just outputs it and I get no error
public class Main extends Application {
public static Stage s;
#Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(FXMLLoader.load(getClass().getResource("Main.fxml")));
stage.setScene(scene);
stage.setTitle("Test");
stage.setResizable(false);
s = stage;
}
}
Controller:
public class Controller {
public void initialize() {
Provider provider = Provider.getCurrentProvider(true);
openSaveDialog(Main.s); //No error
HotKeyListener l = hotKey -> {
Main.s.show();
openSaveDialog(Main.s);
//Returns an error
};
provider.register(KeyStroke.getKeyStroke("control alt D"), l);
}
public File openSaveDialog(Stage s) {
FileChooser chooser = new FileChooser();
chooser.setTitle("Select the output");
return chooser.showSaveDialog(s);
}
}

If you try wrapping the contents of the HotKeyListener in a call to Platform.runLater() this should fix it. Since you're modifying the JavaFX Scene graph, this work must be done on the Application thread.
HotKeyListener l = hotKey -> {
Platform.runLater(() -> {
Main.s.show();
openSaveDialog(Main.s);
});
};
Although, instead of using the AWT HotKeyListener, you should really register a key listener through JavaFX's events. Then you wouldn't need to call Platform.runLater()

Related

Java Fx Splash screen with spring boot

JavaFX 11 and Spring Boot 2.0.
I want to display a splash screen until Spring inits all of its necessary beans, and in the spring.run() I want to close the splash stage(or at least after x amount of seconds). Such that connecting to the DB creating POJOs etc. But when I try to show my splash screen before FX thread kicks in so it throws:
Caused by: java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = main()
I even tried in the Platform.runLater() but still did not work out. Is there any work around for this problem? Thanks.
public class StartUp extends Application{
public static void main(String[] args) {
loadSplashScreen();
appContext = SpringApplication.run(StartUp.class);
launch(args);
}
#Override
public void start(Stage stage) {
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
stage.setScene(new Scene(root, 300, 275));
stage.show();
}
static void loadSplashScreen() {
Stage splashStage = new Stage();
try {
BorderPane splashPane = FXMLLoader.load(getClass().getResource("splash.fxml"));
Scene splashScene = new Scene(splashPane);
splashStage.setScene(splashScene);
splashStage.show();
setFadeInOut(splashPane, splashStage);
} catch (IOException e) {
e.printStackTrace();
}
}
static void setFadeInOut(Parent splashScene, Stage splashStage) {
FadeTransition fadeIn = new FadeTransition(Duration.seconds(3), splashScene);
fadeIn.setFromValue(0);
fadeIn.setToValue(1);
fadeIn.setCycleCount(1);
FadeTransition fadeOut = new FadeTransition(Duration.seconds(3), splashScene);
fadeOut.setFromValue(1);
fadeOut.setToValue(0);
fadeOut.setCycleCount(1);
fadeIn.play();
fadeIn.setOnFinished((e) -> fadeOut.play());
fadeOut.setOnFinished((e) -> splashStage.close());
}
}
In your code you have a method called loadSplashScreen() which you call before the Application.launch(). It will be the call to the launch method that starts the JavaFX thread which is why your loadSplashScreen() method fails i.e. the JavaFX thread hasn't even started when this method is called.
You might want to take a look here at this Oracle tutorial on PreLoaders to understand how to understand a basic example before you try work with starting JavaFX with Spring Boot.
Although I haven't booted JavaFX from Spring Boot, I have done similar in an OSGi bundle and you might like to take a look at my FlexFx GitHub repo here which might give you a few pointers on how to use a pre-loader with Spring Boot but note I currently do not have the ability to display a splash screen in my project.
Finally, your issue would happen on JavaFX-8, 9 or 10. It's not specific to JavaFX-11.

How to catch TableView creation in JavaFX 8

I have a JavaFX application with a TableView that I need to fill up with data once the application starts. I am starting the application in the following manner:
private LayoutController theController;
#Override
public void start(Stage primaryStage)
{
try {
FXMLLoader fxmlload = new FXMLLoader(getClass().getResource("Sample.fxml"));
BorderPane root = (BorderPane )fxmlload.load();
Scene scene = new Scene(root,640,480);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
theController = (LayoutController )fxmlload.getController();
primaryStage.setTitle("Title Application");
primaryStage.addEventHandler(WindowEvent.WINDOW_SHOWN,theController.windowStarted);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
In my controller, called SampleController, I have the TableView object so that it (initially) creates some columns once it is up:
#FXML Parent myRoot;
#FXML TableView datTable<DataClass>;
private Stage theStage;
public EventHandler<WindowEvent> windowStarted = event -> {
theStage = (Stage )myRoot.getScene().getWindow();
getData();
};
protected void getData()
{
dataTable.setEditable(false);
.
// Call a SOAP service to get the data
.
}
I had assumed that once the Stage's WINDOW_SHOWN event occurs, the controls are created and I can do things with them. That apparently isn't the case. Apparently, controls specified using FXML are actually created sometime after the main application window is created!
What happens is that when the windowStarted lambda is executed, the getData() method gets called, but apparently the dataTable was not created before the WINDOW_SHOWN event occurred. As a result, I get NullPointerException failures when I try to call any of dataTable's methods!
I need to catch when the dataTable actually gets created so that I can use its methods. Is there some way to do this?
Someone please advise...
Put your code for data download in method initialize which is called on controller after its root element has been completely processed or in other words after all FXML field are assigned.
#FXML
public void initialize() {
//Here!
}

JAVAFX how to update GUI elements (nodes)

This question is already asked but i copuldnt udnerstand it. Imagine that. I have a program with 2 scenes. First scene1 is opened which is the scene for connection to database. There is a label called Status which should turn from "Disconnected" to "Connected" when the connection is established(by clicking the COnnect button). So i made a function to take the button "Connect" onClick event. This function is declared and defined inside a controller class (i am using fmxl designing with scene builder). So basicly i want to change the status to "COnnected" (status.setText("Connected")) from the connection function(method) which is inside the controller class. However when I do that, the text isn't changed instantly after the connection is established, but it changes when the scene is about to close and i am about to change the scene to the new one... I read on the internet and i saw that i should use Platform.runLater and threading so i tried:
private void changeSC() throws IOException, InterruptedException, SQLException
{
dbConnect();
Thread thrd = new Thread() {
public void run() {
Platform.runLater(new Runnable() {
#Override public void run() {
status.setText("Connected");
status.setTextFill(Color.GREEN);
}});
}
};
thrd.start();
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Thread.sleep(2000);
Main.window.setScene(primary);
}
changeSC is the function that is executed when Connect button is clicked. This is my old version which also doesnt work:
private void changeSC() throws IOException, InterruptedException, SQLException
{
dbConnect();
status.setText("Connected");
status.setTextFill(Color.GREEN);
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Thread.sleep(2000);
Main.window.setScene(primary);
}
The problem is with the text which should change to "Connected". It changes just when my scene is about to be switched....
You need to use a Task, if you have some long running operation. Otherwise when that operation is called from the JavaFX Application Thread it would block the GUI.
If you want to update the GUI from the Task you have to use Platform.runlater, which will run the code on the JavaFX Application Thread:
Platform.runlater
Updates to the Nodes of your GUI have always to be performed on the JavaFx Thread.
When you update status inside the Listener of button it should work.
button.setOnAction(evt -> {
dbConnect();
status.setText("Connected");
// ...
});
If dbConnect() takes some time, you can use a Task:
Task<Void> longRunningTask = new Task<Void>() {
#Override
protected Void call() throws Exception {
dbConnect();
Platform.runLater(() -> status.setText("Connected"));
return null;
}
};
button.setOnAction(e -> {
new Thread(longRunningTask).start();
});
Since you are connecting to a database, you should put this code to run in background using Task or a Service to keep the GUI Thread responding to the user inputs. Just remember that only in the GUI Thread you can update the view state (changing the value of a text in your case). You can use a java Thread and use Platform.runLater which means that the code inside is schedule to be precessed by the GUI Thread but in your case you are using in the wrong way. First the logic to connect to the database should be inside the method run of the thread and once the method finish, set the value of the text and do whatever you want after. Also you'll want to show the new Scene when all the process has been finished to get a chance to the user to see the change in the text. You can change your code in this way:
private void changeSC() throws IOException, InterruptedException, SQLException
{
Thread thrd = new Thread() {
public void run() {
dbConnect();
Platform.runLater(new Runnable() {
#Override public void run() {
status.setText("Connected");
status.setTextFill(Color.GREEN);
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Main.window.setScene(primary);
}
});
}
};
thrd.start();
}
If you choose use a Task you dont have to deal with Platform.runLater explicitly. You only need to create a task (an implementation of the class Task), wrap this inside a java Thread, start it and the set a handler for the different events (eg: setOnSucceeded). This is your code using Task:
private void changeSC() throws IOException, InterruptedException, SQLException
{
Task<Void> task = new Task<Void>(){
#Overrdie
protected Void call() {
dbConnect();
return null;
}
};
//start Task
Thread t = new Thread(task);
t.setDaemon(true); // thread will not prevent application shutdown
t.start();
task.setOnSucceeded(event -> {
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Main.window.setScene(primary);
});
}
ok i fixed it by setting task.SetonFailed() :)

JavaFX Stage shows empty scene

I've been trying to open a new Window in order to display a progress bar, from a controller :
Stage fenetre = new Stage();
fenetre.initModality(Modality.APPLICATION_MODAL);
FXMLLoader loader;
Parent root;
Scene chargementBox;
loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/views/Chargement.fxml"));
loader.load();
root = loader.getRoot();
chargementBox = new Scene(root);
fenetre.setTitle("Chargement");
fenetre.resizableProperty().set(false);
fenetre.setScene(chargementBox);
fenetre.show();
It shows the window. But it's empty :
This is what it should show
This is what i got instead
I tried everything, I used other FXML files, the window sizes are correct but it's always empty. The same code works on other Controllers, but not here.
Help please. There is no exceptions and no errors. Thank's
Edit : I've found the reason why it doesn't show, it's because later in the code i have this function : Transport.send(message); that blocks the program from refreshing my scene and displaying the elements. Is there a way i can run that line in the background or in another thread (I never used threads before.) Thank's again for the help.
To run something on the background thread you need to either use a task (useable once) or a service (reusable).
This is how you can use a service:
Service<Void> service = new Service<Void>(){
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
//do your logic here.
return null;
}
};
}
};
service.setOnSucceeded(event -> {
//do some processing when complete
});
service.setOnFailed(event -> {
//do some processing when it failes
});
service.start();

Could not initialize class javafx.stage.screen

I'm trying to launch 2 javaFX applications, obviously Application#launch() can only be called once per JVM.
After some browsing one told me to manually create a Scene and call Application#start() for the second Application, and so I did:
public class Launcher extends Application {
private Stage primaryStage;
#Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
this.primaryStage.setResizable(false);
this.primaryStage.setTitle("title");
initLayout();
}
public void initLayout() throws IOException {
Parent root = FXMLLoader.load(Launcher.class.getResource("myFile.fxml"));
Scene scene = new Scene(root, 450, 300);
this.primaryStage.setScene(scene);
this.primaryStage.show();
}
}
And loading it (from another class) with:
try {
Application launcher = new Launcher();
launcher.start(new Stage());
} catch (Exception e) {}
Though this results in an error saying
Exception in thread "Thread_number" java.lang.NoClassDefFoundError: Could not initialize class javafx.stage.Screen
at javafx.stage.Window.<init><Unknown Source>
at javafx.stage.Stage.<init><Unknown Source>
at javafx.stage.Stage.<init><Unknown Source>
at javafx.stage.Stage.<init><Unknown Source>
at classILaunchedFrom.methodLaunchedFrom<Main.java:lineNumber>
Does anyone know what I'm doing wrong, because I'm completely at a loss here. Been cracking my skull far too long with javaFX now.
Why do you want to completely separate applications? Just make a class that extends stage for your second window and then call it's .show() method from inside the main javafx application thread. Javafx is rather picky when doing things, only one application instance at a time, all work must be done on the same thread and so on.

Categories