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();
Related
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()
Using JavaFX and FXML I'm trying to display a screen with some basic information, and then when an HTTP call returns, update that screen. What happens instead is the screen is not displayed at all until the call returns. Below is minimum case test of the problem, with a delay meant to simulate the HTTP call.
I'd expect the screen to be displayed, the first label updated, a ten second delay, and then the second label updated. Instead, the screen isn't displayed until after the delay has finished, and that happens no matter where I put the delay. I'm hoping I'm overlooking something simple instead of having to create multiple threads to do something so simple. Below is enough code I think for anyone able to answer the problem. I can include more code if needed.
#Override
public void start(Stage stage) throws IOException {
this.stage = stage;
FXMLLoader loader = new FXMLLoader();
loader.setLocation(App.class.getResource("primary.fxml"));
anchroot = (AnchorPane) loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(anchroot);
stage.setScene(scene);
// Give the controller access to the main app.
PrimaryController controller = loader.getController();
controller.setMainApp(this);
stage.show();
//change the first label
controller.setLabel0();
//timer to simulate IO
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
//try to change the second label 10 sec later
controller.setLabel1();
}
Calling TimeUnit.SECONDS.sleep(10); will make JavaFX thread to block for 10 seconds. In this case you will not be able to see any changes in the GUI thread until the sleep period is finished. In JavaFX, you can use a Timeline to make updates after a certain period:
controller.setLabel0();
new Timeline(new KeyFrame(Duration.seconds(10), event -> controller.setLabel1())).play();
I want to open a new window from the task but for some reason after line Stage stage = new Stage the code stops executing, but there is no error.
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("sample2.fxml"));
Stage stage = new Stage();
System.out.println("Print");
stage.setTitle("My New Stage Title");
stage.setScene(new Scene(root, 100, 100));
stage.show();
return null;
}
};
It never prints out the message 'Print'.
Answer to Question
The reason your Task is failing is because you are creating a Stage on a thread other than the JavaFX Application Thread. The Javadoc of Stage states:
Stage objects must be constructed and modified on the JavaFX Application Thread.
This means when you attempt to create a Stage on the background thread that the Task is running on it will result in an IllegalStateException with a message telling you that you aren't on the JavaFX Application Thread. To solve this issue wrap all code that creates and/or modifies a Stage in a Platform.runLater(Runnable) call.
Side Note: It would probably be better to not create the Stage in the Task at all. Rather, in your case, simply return the result of FXMLLoader.load(URL) and create the Stage when handling the success of the Task.
Task<Parent> task = new Task<Parent>() {
#Override
protected Parent call() throws Exception {
return FXMLLoader.load(getClass().getResource("sample2.fxml"));
}
};
task.setOnSucceeded(event -> {
Parent root = task.getValue();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.show();
};
Why No Error Shown?
You say there is no error but you also don't show any code that would display an error if one does occurr. When a Task fails it sets the cause of failure in the exception property. To handle the case when a Task fails you can:
Listen to the exception property
Add an EventHandler to handle a WorkerStateEvent.WORKER_STATE_FAILED event and query the exception property
Either using task.setOnFailed(EventHandler) or task.addEventXXX(EventType, EventHandler) where XXX is either Filter or Handler
Override the protected void failed() method in your Task implementation and query the exception property
The failed() method will always be called on the JavaFX Application Thread
Catch and handle the exception in the call() method before re-throwing it
Possibly other ways I'm not currently thinking of
You need an Executor to start the thread
Executor exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t;
});
exec.execute(task);
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!
}
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() :)