1

In case a button is added dynamically into a layout, the getWidth property adds back 0; However, the preferred size is reachable instantly. I'm assuming it's because the system didn't have a chance to calculate the size of the button ( since it's just added ).

Minimum reproducible example:


import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class AgentApp extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        Stage tagTest = new Stage();
        VBox topBox = new VBox();
        Parent tagRoot = topBox;
        Button btn = new Button("Add a Button dynamically");
        btn.setOnAction(event -> {
            Button dBtn = new Button("How big is this?!");
            dBtn.setOnAction(event1 -> System.out.println("Width is actually: " + dBtn.getWidth())); /* (1) */
            topBox.getChildren().add(dBtn);
            System.out.println("Width:" + dBtn.getWidth()); /* (2) */
        });
        topBox.getChildren().add(btn);
        Scene tagsScene = new Scene(tagRoot,400,200);
        tagTest.setScene(tagsScene);
        tagTest.show();
    }
}

at (1) the width of the button is printed out correctly, while the width at (2) prints out 0.0. Which is unexpected.

Calling the layout function on the parent of the node ( topbox ) yields no results; Neither inheriting and calling the protected function layoutChildren in a custom container.

But somewhere down the line I assume the size itself must be calculated somewhere, since the size is calculated by the time a dBtn is pressed. How can the size calculation be forced at that point?

UPDATE:

Asking for the width asynchronously returns the correct size:

Button dBtn = new Button("How big is this?!");
dBtn.setOnAction(event1 -> System.out.println("Width is actually: " + dBtn.getWidth()));
topBox.getChildren().add(dBtn);
Platform.runLater(() -> System.out.println("btn width: " + btn.getWidth()));

But since that's an asynchronous call to be run in an unspecified time, the size is still not available instantly.

Dávid Tóth
  • 2,788
  • 1
  • 21
  • 46
  • don't quite understand what you are after: actual size is only available after showing (as you probably see when doing the printout after show) – kleopatra Mar 18 '21 at 15:43
  • in situations when the user interface is not static, but generated dynamically: the size of a newly added button is required for some UI related calculations. – Dávid Tóth Mar 18 '21 at 15:48
  • What happens if you move your print statement after `tagTest.show();` – SedJ601 Mar 18 '21 at 15:53
  • It prints the size correctly, but that doesn't work if the button is added by another button ( for example ) dynamically, after `show();` has been called. – Dávid Tóth Mar 18 '21 at 15:56
  • sounds like your example doesn't fit your description? – kleopatra Mar 18 '21 at 16:34
  • THank you! I updated the code to reflect on that, I hope this makes a better example. – Dávid Tóth Mar 18 '21 at 16:44
  • Why do you need to know it’s size? – James_D Mar 18 '21 at 17:21
  • I can describe it, but workarounds won't answer the question... I'd like to know because I am implementing something like the tags field in SO, by typing into a textfield and adding buttons into a flowPane in front of it. After a tag is recognized, I'd like to extend the text with spaces depending on the size of the button in front of it ( which contains other stuff too, like icons ). – Dávid Tóth Mar 18 '21 at 17:27
  • 1
    What do you mean by "extend the text with spaces?" This sounds like you're looking to do more manual layout than you should need to. Perhaps providing a [mcve] would make your goal and issue more apparent. – Zephyr Mar 18 '21 at 18:42
  • 1
    The size is usually assigned to a node when its parent's `layoutChildren()` method is called. So the surefire way to get the correct size would be to subclass the pane, override `layoutChildren()`, call `super.layoutChildren()`, and then query the size of the node. Generally the `layoutChildren()` method is the place where layout calculations should be performed. For almost all use cases, you can use existing layout panes to manage the layout, instead of defining your own. – James_D Mar 18 '21 at 18:52
  • Hi James, thank you for your input! I did what you suggested and implemented a custom VBox in which `layoutChildren` is public instead of protected, but it still has no effect on the size at the moment of adding it into the scene. – Dávid Tóth Mar 19 '21 at 07:01
  • @Zephyr Thank you for the input but I'd rather not complicate the question with a usecase irrelevant to the question, which is about library behavior. I updated the example and added some details to the question, do you think it is acceptable now? – Dávid Tóth Mar 19 '21 at 07:10
  • as you noted, sizing/locating is only available _after_ the next layout pulse. Don't quite get why you would need it earlier? Why would you need to know the size when inserting a button during/after typing? That should be done by the layout automatically. Anyway, the example doesn't demonstrate your problem, just expected behaviour :) – kleopatra Mar 19 '21 at 10:54
  • are you saying there is no way to force size calculation other than waiting for the next layout pulse? I would also accept this as an answer, if that is the intended behavior ( or rather missing functionality ) in Javafx; and would base design decisions on this fact. Frankly I agree that the need of this specific functionality implies a fault with the design of the program. – Dávid Tóth Mar 19 '21 at 11:59
  • There's no way to force a size calculation, because the ultimate size of the button depends on the amount of space available to it, which depends on, among other things, the size of its parent and sibling nodes. So it's not possible to know its size without a layout pass happening. – James_D Mar 19 '21 at 18:33
  • Indeed, this is what I supposed. Do you think there is any way to force that layout pass? – Dávid Tóth Mar 20 '21 at 12:55

1 Answers1

-2

you have to add the Node into the Scene before calling the getWif