javauser-interfacejavafx

Align nodes at bottom of container, then center container in scene w/ JavaFX


The line vbox.setAlignment(Pos.BOTTOM_CENTER) appears to override stackpane's ability to lay out nodes in the center of the scene.

I'm trying to create a bar chart (of letters in a .txt doc), so I need the bars to grow from the bottom of their container (a vbox). I put the vbox in an hbox, and I want the hbox centered in the scene. I thought putting the hbox in a stackpane would center it, but instead it sets at the bottom center of the scene (so stackpane has no effect).

enter image description here

`// Previous code containing arrays letterCount[] & alphabet[]

HBox hbox = new HBox();
hbox.setSpacing(5);
hbox.setAlignment(Pos.CENTER); //Centers hbox at the bottom, not the middle??

for (int i=0; i<26; i++) {
    Rectangle bar = new Rectangle(10, letterCount[i]); 
    bar.setStroke(Color.BLACK);
    bar.setFill(Color.TRANSPARENT);
    Label label = new Label(String.valueOf(alphabet[i]));
            
    VBox vbox = new VBox(5);
    vbox.setAlignment(Pos.BOTTOM_CENTER); // THIS OVERRIDES STACKPANE??

    vbox.getChildren().addAll(bar, label);
    hbox.getChildren().add(vbox);
}
                    
StackPane sp = new StackPane(hbox);
sp.setAlignment(Pos.CENTER);
Scene scene = new Scene(sp, 500, 500);
// etc.`

NOTE: I tried using VBox.setVgrow(bar, Priority.ALWAYS) instead of setting vbox's alignment to Pos.BOTTOM_CENTER, but it didn't make any difference.


Solution

  • From the documentation for StackPane:

    The stackpane will attempt to resize each child to fill its content area. If the child could not be sized to fill the stackpane (either because it was not resizable or its max size prevented it) then it will be aligned within the area using the alignment property

    and from the documentation for HBox, the maximum height of an HBox is Double.MAX_VALUE.

    Consequently, the HBox fills the entire stack pane.

    Similarly, the VBoxs will fill the entire height of the HBox (which is the height of the StackPane), and since they align their content at the bottom, everything appears at the bottom of the stack pane.

    Try limiting the maximum height of the HBox to be the preferred height:

    hbox.setMaxHeight(Region.USE_PREF_SIZE);
    

    Here is a complete working example:

    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Region;
    import javafx.scene.layout.StackPane;
    import javafx.scene.layout.VBox;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    import java.util.Random;
    
    public class HelloApplication extends Application {
        @Override
        public void start(Stage stage) throws IOException {
            String[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
            Random rng = new Random();
            int[] letterCount = new int[alphabet.length];
            for (int i = 0 ; i < letterCount.length; i++) letterCount[i] = rng.nextInt(200);
            HBox hbox = new HBox();
            hbox.setSpacing(5);
            hbox.setAlignment(Pos.CENTER); 
            hbox.setMaxHeight(Region.USE_PREF_SIZE);
    
            for (int i=0; i< alphabet.length; i++) {
                Rectangle bar = new Rectangle(10, letterCount[i]);
                bar.setStroke(Color.BLACK);
                bar.setFill(Color.TRANSPARENT);
                Label label = new Label(String.valueOf(alphabet[i]));
    
                VBox vbox = new VBox(5);
                vbox.setAlignment(Pos.BOTTOM_CENTER); 
    
    
                vbox.getChildren().addAll(bar, label);
                hbox.getChildren().add(vbox);
            }
    
            StackPane sp = new StackPane(hbox);
            sp.setAlignment(Pos.CENTER);
            Scene scene = new Scene(sp, 500, 500);
    
            stage.setScene(scene);
            stage.show();
        }
    
        public static void main(String[] args) {
            launch();
        }
    
    }
    

    which produces the desired results:

    enter image description here

    There are various other options. E.g. calling

    hbox.setAlignment(Pos.BOTTOM_CENTER);
    hbox.setFillHeight(false);
    hbox.setMaxHeight(Region.USE_PREF_SIZE);
    

    will prevent the VBoxs from growing beyond their preferred size, and position them at the bottom (horizontally centered) of the HBox. Then you can omit setting the alignment on the VBox (since the VBox will be just big enough to hold its content, so it won't be able to align anything anyway).