javajavafxjavafx-css

How to get the height of the row with Text when font size is known in JavaFX?


I have Text nodes in HBox and I need to know the exact height of the row before the row is shown (without using pulse listener). This is my test code:

public class Test1 extends Application {

    @Override
    public void start(Stage primaryStage) {
        Font systemFont = Font.font("System", 14);
        VBox root = new VBox(
                createBox("red", systemFont),
                createBox("yellow", systemFont),
                createBox("red", systemFont)
        );
        Scene scene = new Scene(root, 300, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private HBox createBox(String color, Font font) {
        var hBox = new HBox();
        hBox.setStyle("-fx-padding: 0; -fx-background-color: " + color);
        var text = new Text("Hello, World!");
        text.setFont(font);
        hBox.getChildren().add(text);
        hBox.heightProperty().addListener((ov, oldV, newV) -> System.out.println("NewHeight:" + newV));
        return hBox;
    }


    public static void main(String[] args) {
        launch(args);
    }
}

This is the result stage:

enter image description here

And this is the output:

NewHeight:17.0
NewHeight:17.0
NewHeight:17.0

As you see, the font size is 14, while the row height is 17, so, we have +3 pixels difference for every row.

Could anyone say how to calculate this difference (before the row is shown) or at least how to remove it - I can add it manually via padding.


Solution

  • The documentation of Font says the size is in points, not pixels:

    The size of a Font is described as being specified in points which are a real world measurement of approximately 1/72 inch.

    Which means having a font with size 14 does not necessarily mean the height of a Text will be the same value. But you can get the height of a Text before it's shown via its layout bounds.

    Text text = new Text("Hello, World!");
    text.setFont(Font.font("System", 14));
    
    double height = text.getLayoutBounds().getHeight();
    System.out.println(height);
    

    Note the height will also be affected by the Text.boundsType property. By default, that property will be set to TextBoundsType.LOGICAL.


    If you want the height of something like an HBox, which contains the Text, before its shown then you can try and make use of methods like Node::autoside(), Node::prefHeight(double), Node::applyCss() (requires the Node to be part of a Scene), Parent::layout(), and so on. However, those methods aren't guaranteed to give the actual height the node when displayed. The size of a node depends on many things:

    Depending on what you really want, the following may be enough:

    HBox box = new HBox(text); // 'text' from previous snippet
    box.autosize();
    System.out.println(box.getHeight());
    

    Or you may need to add your HBox to a Scene, call applyCss() and layout() on the root, then get the height. But to be truly accurate in this case, you'd basically need to recreate the real scene graph. You might as well just add the HBox to the real Scene and get its height after a layout pulse at that point. You can simply observe the Region.height or Node.layoutBounds property if you always need the most up-to-date value. If there are transformations involved (e.g., scaling) and you want the visual height of a node, then observe the Node.boundsInParent property.

    That said, it's not often one needs to calculate the height of an arbitrary node ahead of time. Perhaps there's another way to do what you want. For instance, if you're trying to create your own layout then don't try to perform size calculations from outside your layout. Instead, subclass Region (or similar) and override the layoutChildren() method. Perform the size calculations in that method.