Search code examples
javajavafxjava-8javafx-8vbox

When does Vbox/Hbox assign coordinate for it's children?


I put rectangle on a vbox on click and I print coordinate of the rectangle. On key pressed I only print the coordinate. Look at this sample code example :

@FXML
private VBox vbox;

@FXML
private AnchorPane anchorpane;

private List<Rectangle> rectangles = new ArrayList<>();

@Override
public void initialize(URL location, ResourceBundle resources) {
    System.out.println("init");

}

@FXML
private void onMouseClick(MouseEvent e) {
    System.out.println("click");
    vbox.getChildren().clear();

    for(int i = 0; i < 3 ; i++){
        Rectangle r = new Rectangle(100.0, 10.0, Color.BLACK);
        r.addEventHandler(KeyEvent.ANY, this::onKeyTyped);
        rectangles.add(r);
        vbox.getChildren().add(r);

        System.out.println(" r Yposition :" + r.getLayoutY() + " or " + r.getBoundsInParent().getMinY() + " or " + r.getBoundsInLocal().getMinY());
        System.out.println("Vbox height : " + vbox.getBoundsInLocal().getHeight());

    }

}

@FXML
private void onKeyTyped(KeyEvent e) {
    System.out.println("Key pressed");
    for (Rectangle r : rectangles){         
        System.out.println(" r Yposition :" + r.getLayoutY() + " or " + r.getBoundsInParent().getMinY() + " or " + r.getBoundsInLocal().getMinY());
        }
    System.out.println("Vbox height : " + vbox.getBoundsInLocal().getHeight());
}

This give me the following output :

click
r Yposition :0.0 or 0.0 or 0.0
Vbox height : 10.0
r Yposition :0.0 or 0.0 or 0.0
Vbox height : 10.0
 r Yposition :0.0 or 0.0 or 0.0
Vbox height : 10.0

Key pressed
 r Yposition :0.0 or 0.0 or 0.0
 r Yposition :10.0 or 10.0 or 0.0
 r Yposition :20.0 or 20.0 or 0.0
Vbox height : 30.0

So I suppose JavaFX assign coordinate of the shape when it prints it on the screen. But how can I have the coordinate of the shape in the Vbox when I refresh it ?


Solution

  • After you add the rectangles to the VBox, if you call inmediately to getBoundsInParent() you get no results.

    The reason for this can be found here:

    Layout and CSS are also tied to pulse events. Numerous changes in the scene graph could lead to multiple layout or CSS updates, which could seriously degrade performance. The system automatically performs a CSS and layout pass once per pulse to avoid performance degradation. Application developers can also manually trigger layout passes as needed to take measurements prior to a pulse.

    So if you really need the bounds of those rectangles in the same pass, you can call:

    vbox.layout();
    

    right after adding each rectangle to the box to get its final position.

    According to Javadoc, layout():

    Executes a top-down layout pass on the scene graph under this parent. Calling this method while the Parent is doing layout is a no-op

    And now this will work as you would expected:

    for(int i = 0; i < 3 ; i++){
        Rectangle r = new Rectangle(100.0, 10.0, Color.BLACK);
        rectangles.add(r);
        vbox.getChildren().add(r);
        vbox.layout();
    
        System.out.println(" r Yposition :" + r.getLayoutY() + " or " + r.getBoundsInParent().getMinY() + " or " + r.getBoundsInLocal().getMinY());
        System.out.println("Vbox height : " + vbox.getBoundsInLocal().getHeight());
    }
    

    Alternatively, you could override the layoutChildren() method of vbox, as it is invoked during the layout pass to layout the children in this parent:

    private VBox vbox=new VBox(){
    
        @Override
        protected void layoutChildren() {
            super.layoutChildren(); 
            for (Rectangle r : rectangles){         
                System.out.println(" r Yposition :" + r.getLayoutY() + " or " + r.getBoundsInParent().getMinY() + " or " + r.getBoundsInLocal().getMinY());
            }
        }
    
    };
    

    It will give you the same result. Note that this won't work with vbox added to the FXML file, since you need to create a new instance of it.