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();
Related
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.
I've been having troubles generating a second stage/scene in my program. I have a first stage/scene that generates at launch, works perfectly with a working button. It is in it's own class and executes at the beginning.
Further along in the program I have a method that I was hoping to launch my next stage from. I had created another class below for the generation of my second stage, and placed the constructor within this method. When the method is executed, a stage is being created, but the scene isn't showing anything, just the stage with the proper title. I've tried adding in simple buttons, labels with text, but nothing shows. Not really sure what could be the cause, but after fiddling for awhile I decided to request some help from you guys. Given my limited knowledge of JavaFX, I was wondering if the first stage is causing problems for the second one? I leave it open and running, should I close it before this second executes? Or is there a better way to create a second stage without creating a whole new class? Is the construction of the secondScene class a possible problem?
Let me know, thanks!
EDIT: I have now added a runtime exception handler in, and it does trigger a runtime exception when this class is created. I'm still a bit new to Java, does this mean the problem lies within the class I've created? Or can it still be something earlier on in the code? I have still not been able to get the simple class to create inside this program effectively, so I'm unable to produce a minimum level of functionality here, but according to other people, the code does work when it stands alone.
public SecondScene() {
Thread t = new Thread(new adminThread());
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t + " throws exception: " + e);
}
});
// this will call run() function
t.start();
Text textOutput = new Text(textToDisplay);
BorderPane borderPane = new BorderPane(textOutput);
Scene secondScene = new Scene(borderPane, 400, 600);
// New window (Stage)
Stage secondWindow = new Stage();
secondWindow.setTitle("Second Window");
secondWindow.setScene(secondScene);
secondWindow.show();
}
public void setRunningText(String text){
this.textToDisplay = text;
}
}
class adminThread implements Runnable {
public void run() {
throw new RuntimeException();
}
}
I'm working on a game in JavaFX, and right now I'm trying to create a loading screen, since loading the assets takes some time. I've created a LoadingPane class that displays several progress bars, and I know for sure that it works. However, in the below code, the loading pane will not be visible until after the loadAssets function, even though I'm adding it beforehand. When I run the below code, I get a blank stage for the time it takes for the assets to load, and then a screen with the completed progress bars.
I haven't been able to find anyone with similar issues, or any sort of refresh or update function to force the scene to display the loading pane before continuing with the program.
Note: I've deleted some irrelevant code setting up keyboard input handling.
public class Main extends Application{
Pane root = new Pane();
Scene mainScene = new Scene(root, Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT, Color.BLACK);
static LoadingPane loadingPane = new LoadingPane(3);
private static int loadingIndex = 0;
public static void main(String[] args) {
if(Constants.DEBUG_MODE)
System.out.println("WARNING: Game has launched in debug mode!");
launch(args);
}
public static void updateProgress(double percent){
loadingPane.setBarLength(loadingIndex, percent);
}
public static void loadAssets(){
RoomLoader.createRooms();
updateProgress(1.0);
loadingIndex++;
ProjectileLoader.load("imgs/projectiles/");
ProjectileLoader.load(Constants.BATTLE_IMAGES_FILEPATH);
updateProgress(1.0);
loadingIndex++;
BattleLoader.createBattles();
updateProgress(1.0);
loadingIndex++;
}
public static void updateProgress(double percent){
loadingPane.setBarLength(loadingIndex, percent);
}
#Override
public void start(final Stage primaryStage) {
//root.getChildren().add(new javafx.scene.image.ImageView(new Image("imgs/loading.png")));
root.setLayoutX(0);
primaryStage.setScene(mainScene);
primaryStage.show();
primaryStage.toFront();
primaryStage.setTitle("Branch");
primaryStage.setResizable(false);
//primaryStage.getIcons().add(new Image("core/imgs/soul/red.png"));
//This allows the closing of the primaryStage to end the program
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>(){
#Override
public void handle(WindowEvent t) {
System.exit(0);
}
});
root.resize(Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT);
primaryStage.getIcons().add(new Image("imgs/icon.png"));
//End GUI setup
//The problem lines
root.getChildren().add(loadingPane);
//refresh root?
loadAssets();
}
}
EDIT: Working Code
For anyone who arrives here with a similar issue, below is the code I used to get this to work:
I replaced this:
//The problem lines
root.getChildren().add(loadingPane);
//refresh root?
loadAssets();
With this:
root.getChildren().add (loadingPane);
Task<Integer> loadingTask = new Task<Integer>() {
#Override protected Integer call() throws Exception {
loadAssets();
return 1;
}
};
loadingTask.setOnSucceeded(new EventHandler<WorkerStateEvent>(){
#Override
public void handle(WorkerStateEvent t){
loadingPane.setVisible(false);
load(); //note: this function sets up the actual game
//updating the GUI, adding game elements, etc
}
});
new Thread(loadingTask).start();
I can't say that this is the best way to go about this, but I can say that it works. Good luck!
You need a separate thread for the update method.
Code runs in a linear fashion, one bit of code runs then the next. With a separate thread, the two “lines” of code can run side by side. The process runs and the GUI updates at the same time.
JavaFX application runs on specific thread called Application Thread, in other to make GUI responsive while doing some expensive operation, like in your case loading assets, you will need to load assets on another Thread that you create yourself or you can use Task which is one of JavaFX classes meant to be used in such use cases.
I suggest you to read about Task in official javadocs
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();
I need the real preferred size (result from the children) of a Pane but I only get -1. Is there any possibility to find it out?
Ok, here a sample code:
The output is -1.0, 0.0, -1.0, -1.0 but obviously the pane with the button has a "preferred" size!? Only the new Thread has as output the "real" size of the pane but not the preferred one because if I resize the window in the 3 seconds it shows the "real" size and not the preferred size of the beginning...
public class ButtonTest extends Application
{
public static void main(String [] a)
{
Application.launch(a);
}
#Override
public void start(Stage stage) throws Exception
{
BorderPane root = new BorderPane();
StackPane pane = new StackPane();
Button b = new Button("Testbutton");
pane.getChildren().add(b);
pane.setStyle("-fx-background-color: red;");
System.out.println(pane.getPrefWidth());
System.out.println(pane.getWidth());
System.out.println(pane.getMinWidth());
System.out.println(pane.getMaxWidth());
root.setCenter(pane);
Scene scene = new Scene(root);
Thread t = new Thread(new Runnable()
{
#Override
public void run()
{
try
{
Thread.sleep(3000);
} catch (InterruptedException e)
{
// TODO: Handle Exception
e.printStackTrace();
}
System.out.println(pane.getPrefWidth());
System.out.println(pane.getWidth());
System.out.println(pane.getMinWidth());
System.out.println(pane.getMaxWidth());
}
});
t.setDaemon(true);
t.start();
}
}
By default StackPane has the PrefWidth and PrefHeight set by the inherited constraint "USE_COMPUTED_SIZE" (witch values is equal to -1) defined in parent class "Region".
This definition means the preferable size will be computed in layout process.
Perhaps your concept is missplaced. Pref values are used to layout process know how it should arrange object in canvas. To know real size of componente you can use methods like I mentioned before 'getBoundsInParent()' or 'getBoundsInLocal()' (Diference between thoose are explained in javadoc).
BUT is important to understand a component just has his size computed during the layout process. Usually, layout process occurs on separated thread than your events or your main() thread. Thats why after sleeping 3 seconds let you do the trick. You can try something like using the following code to let your code be executed in the JavaFX GUI thread (started on your last line of code):
Platform.runLater(new Runnable() {
#Override public void run() {
<Your code in GUI Thread>
}
});
Hope it can give a little help. Sorry for not offer you a sample code, but I'm not in a machine with environment installed. Will try to better my answer latter.
Good luck.
(Please anyone, feel free to correct my english, I know it's terrible)
Ok, thank you very much for this explination. But the other methods don't return the right result, too...
Maybe this case helps to understand what my goal is:
One related problem is that I have a SplitPane, two parts, now I hide the second part (divider position = 1.0) and show only the first one, sometimes I like to show also the second part. I do this over repostioning of the dividers. But at any time it can happen, that the second part can be updated with new elements (=> update of the necessary size). Now, to reposition the divider (with an animation) I need the preferred size of the (hidden) second part?! Without real code for that do you have any idea how to solve the problem?
Update:
I solved the issue with the "real size" adding a public doubleproperty to the pane and bind it to the widthproperty of the pane. Now I could add a changelistener to this doubleproperty in the splitpane. After first time being visible I got the right pixel size.
public DoubleProperty realWidth;
pane.setPrefWidth(USE_PREF_SIZE);
pane.setMaxWidth(USE_PREF_SIZE);
realWidth = new SimpleDoubleProperty(0.0);
realWidth.bind(this.widthProperty());
=> Add a change listener from somewhere else
But know I have the problem that if I add a button (after 3 seconds) to the pane the splitpane (the changelistener on this realWidth property) is not notified...
Update: It is notified :D But not adding a button but adding e. g. a stackpane works as expected. Many thanks for your help to understand how it works!