JavaFX Get preferred size of a Pane - java

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!

Related

Why does my JavaFX have this program flow problem?

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();

JavaFX, problem with Platform.runLater(), delayed rendering of Canvas graphic

I am trying to make a simple event driven TicTacToe game using JavaFX. Currently I am struggling with termination once certain conditions are met. To put it into more details, the player can click on the GridPane elements, which are Canvas Objects, and then they are filled with "X" or "O" shapes respectively (I am using strokeLine and strokeOval methods of GraphicsContext). Code below:
private static void placeX(Canvas square){
GraphicsContext gc = square.getGraphicsContext2D();
gc.setLineWidth(10.0f);
gc.setStroke(Color.CORNFLOWERBLUE);
gc.strokeLine(square.getWidth()*0.2, square.getHeight()*0.2, square.getWidth()*0.8, square.getHeight()*0.8);
gc.strokeLine(square.getWidth()*0.8, square.getHeight()*0.2, square.getWidth()*0.2, square.getHeight()*0.8);
}
Once 3 of the same shapes appear in line or diagonally the program should terminate. I am doing this using Platform.exit(). Code below:
class HandleGame implements EventHandler<MouseEvent>{
#Override
public void handle(MouseEvent e){
Canvas can = (Canvas)e.getTarget();
//function to check if the canvas is clear
placeX(can);
if(game.isEnded()){ //checks if the same shape appears three times
Platform.runLater(new Runnable() {
#Override
public void run(){
try {
Thread.sleep(5000);
}
catch(InterruptedException exc){
System.out.println("Got something: " + exc.getMessage());
}
Platform.exit();
}
});
}
}
}
This event handler is attached to every Canvas object in GridPane and triggers on mouse release. The problem I am having is that once the last Canvas is clicked, before the shape appears on the Canvas the specified Runnable is executed and the rendering is unnaturally delayed (the "X" shape appears only for a second before closing). Strangely enough 1 out of 10 runs it executes as expected. How can I make the rendering trigger before the Thread.sleep() and following Platform.exit()? Why on rare occasions the rendering is actually performed before Thread.sleep()? I did a little research but could not find anything decisive, I am newbie when it comes to JavaFx. Appreciate your help.
Based on #Slaw and #VGR comments I managed to solve the problem using two different methods, one utilizing PauseTransition and second Thread. Code below:
Using PauseTransition:
if(game.isEnded()){
PauseTransition termination = new PauseTransition(Duration.seconds(1d));
termination.setOnFinished(event -> Platform.exit());
termination.play();
}
Using Thread:
if(game.isEnded()){
new Thread(() -> {
try{
Thread.sleep(2000);
}
catch(InterruptedException exc){
; //exception handling code here
}
Platform.exit();
}).start();*/

Issue with JavaFX second scene/stage creation

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();
}
}

JavaFX Update Issue?

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

JFrame won't disappear in time for screenshot to be taken

This utility application takes a screenshot of multiple monitors by pressing a button on a JFrame. The intended logic of this method is as follows:
Create new Rectangle to represent a union of all the monitors' boundsm
Use InvokeAndWait to hide to JFrame, and ensure it is hidden before processing any further
Take a screenshot using the Robot class
Set the frame to visible again and return the image
Even with the final "return frame to visible" step commented out of the code, leaving me seeing no frame on the screen after execution, the frame is visible in the screenshot. I do not know why.
Using print statements, I have determined that the firing of the method to turn the JFrame invisible does run before taking the screenshot. I also attempted to use an if statement to check if the JFrame was visible before taking the screenshot, and the if statement was never triggered.
What is the solution to this?
public Image capture(ArrayList<Monitor> monitors) {
Rectangle bounds = new Rectangle();
monitors.stream().forEach(a -> Rectangle.union(bounds, a.getBounds(), bounds) );
Image image = null;
try {
EventQueue.invokeAndWait(() -> {
frame.setVisible(false);
System.out.println("Set frame invisible");
});
} catch (Exception ex) {
ex.printStackTrace();
}
try {
image = new Image(new Robot().createScreenCapture(bounds));
} catch (Exception ex) {
ex.printStackTrace();
}
//frame.setVisible(true);
return image;
}
This is not a definitive answer, but maybe it'll point you in the right direction.
I had a similar problem requiring a JFrame to go fullscreen on a 3D application using OpenGL.
What I assume is happening is that frame.setVisible() talks to the OS window manager requesting the window be hidden (or shown). Once the request is made and acknowledged, the method call is no longer blocking, and the thread in invokeAndWait() is now done invoking and waiting.
Your code proceeds to a take a screenshot, but the operating system is not guaranteed to have actually processed the request to minimize the Window. In your case, it appears that is doesn't.
The Solution (maybe?):
It looks like Java has a class called a WindowEvent doc here. You should be able to create a listener and/or a loop before the screenshot call that waits for a status update. Note the doc specifically says:
The window-deactivated event type. This event is delivered when the Window is no longer the active Window. Only a Frame or a Dialog can be the active Window. The native windowing system may denote the active Window or its children with special decorations, such as a highlighted title bar. The active Window is always either the focused Window, or the first Frame or Dialog that is an owner of the focused Window.
I suspect waiting for the WINDOW_DEACTIVATED and/or WINDOW_LOST_FOCUS could be the actual indicator of if the window has been minimized by the OS window manager.
Again, I'm basing this off of a mostly unrelated project I worked on several months ago, but hopefully some of this will help.
UPDATE
OP Implemented a Solution as follows:
public Image capture(ArrayList<Monitor> monitors)
{
Rectangle bounds = new Rectangle();
monitors.stream().forEach(a -> Rectangle.union(bounds, a.getBounds(), bounds) );
Image image = null;
try
{
EventQueue.invokeAndWait(() -> frame.setExtendedState(Frame.ICONIFIED));
while (frame.getExtendedState() != Frame.ICONIFIED) { }
image = new Image(new Robot().createScreenCapture(bounds));
}
catch (Exception ex)
{
ex.printStackTrace();
}
EventQueue.invokeLater(() -> frame.setExtendedState(Frame.NORMAL));
return image;
}

Categories